You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:19:50