
本文讲解如何在Go中正确读取FFmpeg子进程的标准输出(如原始H.264流),避免因忽略Read返回字节数导致数据截断,并提供基于io.Read循环和io.Copy两种健壮方案,适用于后续自定义字节处理(如UDP转发、帧修改等场景)。
本文讲解如何在go中正确读取ffmpeg子进程的标准输出(如原始h.264流),避免因忽略`read`返回字节数导致数据截断,并提供基于`io.read`循环和`io.copy`两种健壮方案,适用于后续自定义字节处理(如udp转发、帧修改等场景)。
在Go中调用FFmpeg进行实时音视频流处理(如UDP输入→FFmpeg解复用/转码→字节流处理→FFmpeg再封装→UDP输出)时,一个常见且关键的陷阱是:错误地假设io.Reader.Read()总会填满整个缓冲区。原始代码中使用 stdout.Read(bytes) 后直接 f.Write(bytes),但 Read 返回的实际读取字节数 n 可能远小于缓冲区长度(例如只读了128字节却写了1024字节),导致写入大量零填充( )或覆盖残留数据,最终造成输出文件损坏(如VLC无法播放、体积异常偏小)。
✅ 正确做法:始终依据 Read 的实际返回值操作
以下为修复后的核心读写逻辑(推荐第一种——显式控制流,便于后续插入字节处理逻辑):
package main
import (
"fmt"
"io"
"os"
"os/exec"
)
func verificaErro(e error) {
if e != nil {
panic(e)
}
}
func main() {
dir, _ := os.Getwd()
cmd := exec.Command("ffmpeg",
"-hide_banner", "-re",
"-i", dir+"\teste-4k.mp4",
"-c:v", "copy", "-c:a", "copy", // 避免转码,保持原始流
"-f", "h264", "-" // 输出原始H.264 Annex B格式(关键!)
)
stdout, err := cmd.StdoutPipe()
verificaErro(err)
err = cmd.Start()
verificaErro(err)
// 确保输出文件存在并可写(注意:O_TRUNC更安全,而非O_APPEND)
outFile, err := os.OpenFile(dir+"/out.h264", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
verificaErro(err)
defer outFile.Close()
// 使用足够大的缓冲区(如40KB),减少系统调用次数
buf := make([]byte, 40*1024)
for {
n, err := stdout.Read(buf)
if n > 0 {
// ✅ 关键:仅写入实际读到的n个字节
if _, writeErr := outFile.Write(buf[:n]); writeErr != nil {
verificaErro(writeErr)
}
}
if err == io.EOF {
break // 流结束
}
if err != nil {
fmt.Printf("Read error: %v
", err)
break
}
}
// ✅ 必须等待FFmpeg进程退出,确保所有数据已刷出
if waitErr := cmd.Wait(); waitErr != nil {
fmt.Printf("FFmpeg process exited with error: %v
", waitErr)
}
}? 关键修复点说明
-
buf[:n]切片:Read返回n表示本次有效字节数,必须用buf[:n]获取真实数据,而非整个buf。 -
O_TRUNC替代O_APPEND:避免文件末尾残留旧数据;O_APPEND在并发写入时有风险,此处单次写入应清空重写。 -
cmd.Wait()不可省略:Start()仅启动进程,Wait()才阻塞至FFmpeg退出,并保证其stdout管道彻底关闭,否则可能丢失末尾数据(如SPS/PPS末帧)。 -
输出格式选择
-f h264:比-f rawvideo更合理(后者输出YUV像素数据,体积巨大且非标准H.264流);h264格式输出符合Annex B规范的NALU流,可被VLC直接播放。
? 进阶方案:使用 io.Copy(适合纯透传场景)
若暂不需逐包处理,io.Copy 是最简洁可靠的替代方案:
// ... 启动cmd、获取stdout后
var buf bytes.Buffer
n, err := io.Copy(&buf, stdout)
verificaErro(err)
fmt.Printf("Copied %d bytes
", n)
// 等待FFmpeg退出
verificaErro(cmd.Wait())
// 写入文件
err = os.WriteFile(dir+"/out.h264", buf.Bytes(), 0644)
verificaErro(err)⚠️ 注意:
io.Copy将全部数据载入内存,对超长视频(如数小时4K流)可能导致OOM;生产环境建议结合io.CopyBuffer或分块流式处理。立即学习“go语言免费学习笔记(深入)”;
? 后续扩展提示
-
UDP流对接:将
outFile.Write(...)替换为udpConn.WriteTo(...)即可发送至目标地址; -
字节处理:在
buf[:n]后插入NALU解析(如查找0x00000001)、帧类型判断、时间戳修改等逻辑; -
错误恢复:捕获
cmd.Process.Signal(os.Interrupt)实现优雅退出; -
性能优化:使用
bufio.NewReader(stdout)+ 自定义缓冲区提升吞吐量。
通过严格遵循I/O返回值语义并补全进程生命周期管理,即可构建稳定、可扩展的Go+FFmpeg实时流处理管道。


















