背景
在视频处理领域,MPEG-2 传输流(TS 流)是一种广泛应用的标准格式,常用于广播电视和流媒体传输。FFmpeg 作为强大的开源音视频处理工具,提供了丰富的 API 来处理 TS 流。通常,我们会将处理后的 TS 流直接输出到文件,但在某些场景下,如实时流媒体传输,我们需要将 TS 流输出到网络或者自定义的缓冲区中,这需要实现自定义 I/O 操作。
实现
FFmpeg 的 AVIOContext
是实现自定义 I/O 操作的关键。AVIOContext
提供了一个抽象的 I/O 层,允许用户自定义读写操作,从而将数据输出到非文件目标,如网络套接字、内存缓冲区等。我们可以通过 avio_alloc_context
函数创建一个自定义的 AVIOContext
,并指定读写回调函数,在回调函数中实现自己的 I/O 逻辑。
以下是一个使用 FFmpeg 写 TS 流且不直接输出到文件的 C++ 代码示例:
ts_writer.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/imgutils.h> }
class TSWriter { public: TSWriter() = default; int open(AVCodecParameters *codecParameters); int close(); int write(AVPacket *pkt);
private: static int write_packet(void *opaque, uint8_t *buf, int buf_size);
private: AVFormatContext *m_formatContext = nullptr; AVIOContext *m_ioContext = nullptr; AVStream *m_videoStream = nullptr; };
|
ts_writer.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| #include "ts_writer.h" #include <iostream> #include <cstring>
extern "C" { #include <libavformat/avformat.h> #include <libavutil/avutil.h> }
constexpr int IO_BUFFER_SIZE = 32768;
int TSWriter::open(AVCodecParameters *codecParameters) { int ret = avformat_alloc_output_context2(&m_formatContext, nullptr, "mpegts", nullptr); if (ret < 0) { std::cerr << "Failed to allocate format context: " << av_err2str(ret) << std::endl; return -1; }
uint8_t* buffer = static_cast<uint8_t*>(av_malloc(IO_BUFFER_SIZE)); if (!buffer) { std::cerr << "Failed to allocate I/O buffer" << std::endl; avformat_free_context(m_formatContext); m_formatContext = nullptr; return -1; }
m_ioContext = avio_alloc_context(buffer, IO_BUFFER_SIZE, AVIO_FLAG_WRITE, this, nullptr, TSWriter::write_packet, nullptr); if (!m_ioContext) { std::cerr << "Failed to allocate I/O context" << std::endl; av_free(buffer); avformat_free_context(m_formatContext); m_formatContext = nullptr; return -1; }
m_formatContext->pb = m_ioContext;
if (!m_videoStream) { m_videoStream = avformat_new_stream(m_formatContext, nullptr); if (!m_videoStream) { std::cerr << "Failed to create new video stream" << std::endl; avio_context_free(&m_ioContext); avformat_free_context(m_formatContext); m_formatContext = nullptr; return -1; }
ret = avcodec_parameters_copy(m_videoStream->codecpar, codecParameters); if (ret < 0) { std::cerr << "Failed to copy codec parameters: " << av_err2str(ret) << std::endl; avio_context_free(&m_ioContext); avformat_free_context(m_formatContext); m_formatContext = nullptr; return -1; }
m_videoStream->codecpar->codec_tag = 0;
ret = avformat_write_header(m_formatContext, nullptr); if (ret < 0) { std::cerr << "Failed to write header: " << av_err2str(ret) << std::endl; avio_context_free(&m_ioContext); avformat_free_context(m_formatContext); m_formatContext = nullptr; return -1; } } return 0; }
int TSWriter::close() { if (m_formatContext) { int ret = av_write_trailer(m_formatContext); if (ret < 0) { std::cerr << "Failed to write trailer: " << av_err2str(ret) << std::endl; } avformat_free_context(m_formatContext); m_formatContext = nullptr; }
if (m_ioContext) { avio_context_free(&m_ioContext); m_ioContext = nullptr; }
return 0; }
int TSWriter::write(AVPacket* packet) { if (!m_formatContext || !packet) { return -1; }
int ret = av_interleaved_write_frame(m_formatContext, packet); if (ret < 0) { std::cerr << "Error writing packet: " << av_err2str(ret) << std::endl; return -1; }
return 0; }
int TSWriter::write_packet(void* opaque, uint8_t* buf, int buf_size) { std::cout << "Writing " << buf_size << " bytes to output" << std::endl; return buf_size; }
|
关键流程:
- 分配输出格式上下文:使用 avformat_alloc_output_context2 分配 MPEG-2 TS 格式的输出上下文。
- 分配 I/O 缓冲区:使用 av_malloc 分配指定大小的 I/O 缓冲区。
- 分配 I/O 上下文:使用 avio_alloc_context 创建自定义 I/O 上下文,指定写回调函数 TSWriter::write_packet。
write_packet方法就可以拿到ts流内存了,这里可以自行选择写入文件或者上传。
总结
通过自定义 I/O 上下文和写回调函数,可灵活控制 TS 流数据的输出,而不局限于文件输出。
demo地址:ts_demo