基于FFmpeg库实现MPEG-TS复用器及LPCM音频UDP封流技术咨询
实现基于libavformat的MPEG-TS复用器(UDP发送缓冲区LPCM音频)
当然可以用FFmpeg的libavformat库实现这个需求!相比只能处理文件输入的命令行工具,libavformat允许你直接从内存缓冲区读取原始LPCM数据,完成编码、MPEG-TS封装后通过UDP发送,完美匹配你的场景。下面是具体的实现思路和代码示例:
核心实现步骤
- 初始化FFmpeg的网络与格式组件(UDP传输依赖网络初始化)
- 配置MPEG-TS输出上下文,指定UDP目标地址
- 创建音频流并设置编码参数(将原始LPCM转换为TS兼容的编码,比如AAC,也可保留LPCM)
- 从缓冲区读取LPCM数据,转换为FFmpeg标准的
AVFrame结构 - 编码音频帧为
AVPacket,写入MPEG-TS输出上下文(自动完成UDP发送) - 控制发送速率模拟实时流(对应命令行的
-re参数)
代码示例(C语言)
#include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/opt.h> #include <libavutil/time.h> // 假设你已有从缓冲区获取LPCM数据的函数 int get_lpcm_buffer(uint8_t *out_buf, int *buf_size, int sample_rate, int channels); int main() { AVFormatContext *fmt_ctx = NULL; AVStream *audio_stream = NULL; AVCodec *codec = NULL; AVCodecContext *codec_ctx = NULL; AVFrame *frame = NULL; AVPacket pkt; int ret; const char *udp_url = "udp://127.0.0.1:5555"; // 你的LPCM实际参数,根据录制配置调整 int sample_rate = 44100; int channels = 2; enum AVSampleFormat lpcm_fmt = AV_SAMPLE_FMT_S16; // 初始化FFmpeg网络组件(UDP必须) avformat_network_init(); av_register_all(); // 兼容旧版本FFmpeg,新版本可省略 // 创建MPEG-TS输出上下文 ret = avformat_alloc_output_context2(&fmt_ctx, NULL, "mpegts", udp_url); if (ret < 0) { fprintf(stderr, "创建输出上下文失败: %s\n", av_err2str(ret)); goto cleanup; } // 选择编码器:示例用AAC(TS常用压缩格式),若要保留LPCM则用AV_CODEC_ID_PCM_S16BE codec = avcodec_find_encoder(AV_CODEC_ID_AAC); if (!codec) { fprintf(stderr, "找不到AAC编码器\n"); goto cleanup; } // 创建音频流 audio_stream = avformat_new_stream(fmt_ctx, codec); if (!audio_stream) { fprintf(stderr, "创建音频流失败\n"); goto cleanup; } // 配置编码器上下文 codec_ctx = audio_stream->codec; codec_ctx->codec_id = AV_CODEC_ID_AAC; codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO; codec_ctx->sample_rate = sample_rate; codec_ctx->channels = channels; codec_ctx->channel_layout = av_get_default_channel_layout(channels); codec_ctx->sample_fmt = codec->sample_fmts[0]; // 使用编码器支持的格式 codec_ctx->bit_rate = 128000; // 设置音频比特率 // 打开编码器 ret = avcodec_open2(codec_ctx, codec, NULL); if (ret < 0) { fprintf(stderr, "打开编码器失败: %s\n", av_err2str(ret)); goto cleanup; } // 初始化存储LPCM数据的AVFrame frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "分配AVFrame失败\n"); goto cleanup; } frame->nb_samples = codec_ctx->frame_size; frame->format = codec_ctx->sample_fmt; frame->channel_layout = codec_ctx->channel_layout; ret = av_frame_get_buffer(frame, 0); if (ret < 0) { fprintf(stderr, "分配帧缓冲区失败: %s\n", av_err2str(ret)); goto cleanup; } // 打开UDP输出URL if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&fmt_ctx->pb, udp_url, AVIO_FLAG_WRITE); if (ret < 0) { fprintf(stderr, "打开UDP地址失败: %s\n", av_err2str(ret)); goto cleanup; } } // 写入MPEG-TS头 ret = avformat_write_header(fmt_ctx, NULL); if (ret < 0) { fprintf(stderr, "写入TS头失败: %s\n", av_err2str(ret)); goto cleanup; } // 循环处理缓冲区数据 while (1) { uint8_t lpcm_buf[8192]; int lpcm_size; // 从缓冲区获取LPCM数据,无数据则退出循环 if (!get_lpcm_buffer(lpcm_buf, &lpcm_size, sample_rate, channels)) { break; } // 将原始LPCM数据填充到AVFrame av_samples_fill_arrays(frame->data, frame->linesize, lpcm_buf, channels, frame->nb_samples, lpcm_fmt, 0); // 设置时间戳,保证流同步 frame->pts = av_rescale_q(frame->nb_samples, (AVRational){1, sample_rate}, audio_stream->time_base); // 发送帧到编码器 ret = avcodec_send_frame(codec_ctx, frame); if (ret < 0) { fprintf(stderr, "发送帧到编码器失败: %s\n", av_err2str(ret)); continue; } // 接收编码后的数据包并发送 while (ret >= 0) { av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; ret = avcodec_receive_packet(codec_ctx, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) { fprintf(stderr, "接收编码包失败: %s\n", av_err2str(ret)); goto cleanup; } // 调整数据包的时间戳和流索引 pkt.stream_index = audio_stream->index; av_packet_rescale_ts(&pkt, codec_ctx->time_base, audio_stream->time_base); // 写入TS封装(自动通过UDP发送) ret = av_interleaved_write_frame(fmt_ctx, &pkt); if (ret < 0) { fprintf(stderr, "写入TS帧失败: %s\n", av_err2str(ret)); av_packet_unref(&pkt); goto cleanup; } // 模拟实时发送速率,对应命令行-re参数 av_usleep(av_rescale_q(pkt.duration, audio_stream->time_base, (AVRational){1, 1000000})); av_packet_unref(&pkt); } } // 写入TS尾 av_write_trailer(fmt_ctx); cleanup: // 资源释放 if (codec_ctx) avcodec_close(codec_ctx); if (frame) av_frame_free(&frame); if (fmt_ctx) { if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) avio_close(fmt_ctx->pb); avformat_free_context(fmt_ctx); } avformat_network_deinit(); return ret >= 0 ? 0 : 1; }
关键注意事项
- 编码格式选择:如果不需要压缩,可将编码器改为
AV_CODEC_ID_PCM_S16BE(对应LPCM格式),但LPCM体积大,不适合网络传输,推荐用AAC。 - 时间戳处理:正确设置
pts是保证流同步的核心,av_rescale_q用于在不同时间基准间转换。 - 实时流控制:通过
av_usleep模拟实时发送速率,避免过快发送导致接收端溢出。 - 缓冲区适配:需根据你实际录制的LPCM参数(采样率、通道数、采样格式)调整代码中的对应配置,确保数据能正确转换为
AVFrame。
内容的提问来源于stack exchange,提问作者Pierre P.
相关产品推荐
相关产品推荐

