# goffmpeg

Go bindings for FFmpeg using CGO.

## 项目简介

本项目提供 Go 语言对 FFmpeg 动态库的 CGO 绑定，以 lib 库形式开放接口供其他应用调用。

## 系统依赖

### FFmpeg 开发库

**Ubuntu/Debian:**
```bash
sudo apt install libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libswscale-dev libswresample-dev pkg-config
```

**验证安装:**
```bash
pkg-config --exists libavcodec libavformat libavutil
echo $?  # 应输出 0
```

## 项目结构

```
goffmpeg/
├── pkg/ffmpeg/           # 核心库
│   ├── cgo.go            # CGO 配置和头文件导入
│   ├── errors.go         # 错误类型定义
│   ├── ffmpeg.go         # FormatContext、Stream、CodecParameters
│   ├── codec.go          # 编解码器封装
│   ├── frame.go          # 帧数据封装
│   └── packet.go         # 数据包封装
├── cmd/ffmpeg-cli/       # CLI 工具
├── examples/             # 示例代码
│   └── simple-transcode/ # 简单转码示例
└── README.md
```

## 构建

```bash
go build ./...
```

## 示例用法

### 简单转码 (流拷贝模式)

```go
package main

import (
    "fmt"
    "log"
    "os"

    "git.kingecg.top/kingecg/goffmpeg/pkg/ffmpeg"
)

func main() {
    inputURL := "input.mp4"
    outputURL := "output.mp4"

    // 打开输入文件
    ic := ffmpeg.AllocFormatContext()
    defer ic.Free()

    if err := ic.OpenInput(inputURL); err != nil {
        log.Fatalf("Failed to open input: %v", err)
    }
    defer ic.Close()

    // 查找流信息
    if err := ic.FindStreamInfo(); err != nil {
        log.Fatalf("Failed to find stream info: %v", err)
    }

    // 打印输入格式信息
    fmt.Printf("Input: %s\n", inputURL)
    ic.DumpFormat(0, inputURL, false)

    // 猜测输出格式
    of := ffmpeg.GuessFormat("", outputURL)
    if of == nil {
        log.Fatal("Failed to guess output format")
    }

    // 创建输出上下文
    ofc, err := ffmpeg.AllocOutputContext(outputURL, of)
    if err != nil {
        log.Fatalf("Failed to allocate output context: %v", err)
    }
    defer ofc.Free()

    // 获取输入流
    inputStreams := ic.Streams()

    // 处理流
    for i, is := range inputStreams {
        cp := is.CodecParameters()
        if cp == nil {
            continue
        }

        // 只处理视频和音频
        if cp.CodecType() != ffmpeg.CodecTypeVideo &&
           cp.CodecType() != ffmpeg.CodecTypeAudio {
            continue
        }

        // 添加输出流 (流拷贝模式)
        os, err := ofc.AddStream(nil)
        if err != nil {
            continue
        }

        // 复制编解码参数
        os.SetCodecParameters(cp)
        os.SetTimeBase(is.TimeBase())

        fmt.Printf("Stream %d: type=%d\n", i, cp.CodecType())
    }

    // 打开输出文件
    if err := ofc.OpenOutput(outputURL); err != nil {
        log.Fatalf("Failed to open output: %v", err)
    }

    // 写入头部
    if err := ofc.WriteHeader(); err != nil {
        log.Fatalf("Failed to write header: %v", err)
    }

    // 转封装循环
    pkt := ffmpeg.AllocPacket()
    defer pkt.Free()

    for {
        err := ic.ReadPacket(pkt)
        if err != nil {
            break
        }

        ofc.WritePacket(pkt)
        pkt.Unref()
    }

    // 写入尾部
    ofc.WriteTrailer()

    fmt.Printf("Remuxing complete! Output saved to: %s\n", outputURL)
}
```

## API 概览

### FormatContext

- `AllocFormatContext()` - 分配格式上下文
- `OpenInput(url)` - 打开输入
- `FindStreamInfo()` - 查找流信息
- `Streams()` - 获取所有流
- `VideoStreams()` - 获取视频流
- `AudioStreams()` - 获取音频流
- `ReadPacket()` / `WritePacket()` - 读写数据包

### OutputFormatContext

- `AllocOutputContext(url, format)` - 分配输出上下文
- `AddStream(codec)` - 添加流
- `OpenOutput(url)` - 打开输出文件
- `WriteHeader()` / `WriteTrailer()` - 写入头部/尾部

### Stream

- `CodecParameters()` - 获取编解码参数
- `SetCodecParameters(cp)` - 设置编解码参数
- `TimeBase()` / `SetTimeBase(r)` - 时间基

### CodecParameters

- `CodecType()` - 编解码器类型
- `CodecID()` - 编解码器 ID
- `Width()` / `Height()` - 尺寸
- `Format()` - 格式
- `SampleRate()` - 采样率
- `Channels()` - 声道数

### Context (编解码器上下文)

- `AllocContext()` - 分配上下文
- `SetCodec(codec)` - 设置编解码器
- `Open(codec)` - 打开编解码器
- `SendPacket()` / `ReceiveFrame()` - 解码
- `SendFrame()` / `ReceivePacket()` - 编码

### Packet

- `AllocPacket()` - 分配数据包
- `Data()` / `Size()` - 数据和大小
- `PTS()` / `DTS()` - 时间戳
- `StreamIndex()` - 流索引

### Frame

- `AllocFrame()` - 分配帧
- `Width()` / `Height()` - 尺寸
- `Format()` - 格式
- `NbSamples()` - 样本数

## 注意事项

1. **CGO 构建标签**: 项目使用 `//go:build cgo` 约束，需要 CGO 支持才能构建
2. **FFmpeg 版本**: 需要 FFmpeg 4.0+
3. **线程安全**: FFmpeg 的某些操作不是线程安全的，请参考 FFmpeg 文档
4. **内存管理**: 使用 `Free()` 方法释放资源，或使用 `defer`

## 许可证

MIT License
