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

如何用FFmpeg C代码将S16原始音频编码为Opus格式?

问题:S16原始音频编码为Opus时ffplay无法播放的解决方法

我使用FFmpeg官方教程的C语言音频编码示例代码,原本将S16格式原始音频编码为MP2(AV_CODEC_ID_MP2)时,ffplay可正常播放;但改为编码Opus(AV_CODEC_ID_OPUS)后,ffplay提示output.opus: Invalid data found when processing input。经确认两个编码器均支持AV_SAMPLE_FMT_S16输入,请问如何实现从uint16_t S16原始格式编码为Opus?


核心问题分析与解决步骤

Opus编码器对输入的要求和MP2存在差异,并非仅支持采样格式即可,需调整以下关键参数与流程:

1. 强制使用Ogg容器封装

Opus裸流无法被ffplay识别,必须封装到Ogg容器中。修改输出格式初始化代码:

// 替换原avformat_alloc_output_context2调用,指定Ogg格式
avformat_alloc_output_context2(&fmt_ctx, NULL, "ogg", "output.opus");

2. 匹配Opus的硬性参数要求

Opus对采样率、声道数有严格限制:

  • 采样率仅支持8000、12000、16000、24000、48000Hz,推荐使用48000Hz
  • 声道数仅支持1(单声道)或2(立体声)
    确保codec_ctx->sample_rate和codec_ctx->channels设置符合上述范围。

3. 明确设置通道布局

Opus要求必须指定通道布局,不能留空:

codec_ctx->channel_layout = codec_ctx->channels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;

4. 修正S16数据的输入细节

虽然Opus支持S16输入,但需注意:

  • 输入的S16数据必须是小端字节序(主机字节序),若原始数据为大端,需用av_bswap16转换
  • 立体声数据需按左-右-左-右的交错格式排列

5. 编译与运行验证

编译时确保链接FFmpeg相关库(需提前编译FFmpeg并启用Opus支持):

gcc encode_opus.c -o encode_opus $(pkg-config --libs --cflags libavcodec libavformat libavutil)

运行后用ffplay播放输出文件:

ffplay output.opus

完整调整后的关键代码片段

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <math.h>

int main(int argc, char **argv) {
    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *codec_ctx = NULL;
    const AVCodec *codec = NULL;
    AVStream *stream = NULL;
    AVPacket pkt;
    AVFrame *frame = NULL;
    int ret, i, j;
    uint16_t *samples;
    // Opus推荐帧大小:48000Hz对应20ms帧长,即960个采样点
    const int nb_samples = 960;

    // 初始化Ogg格式上下文
    ret = avformat_alloc_output_context2(&fmt_ctx, NULL, "ogg", "output.opus");
    if (ret < 0) goto end;

    // 查找Opus编码器
    codec = avcodec_find_encoder(AV_CODEC_ID_OPUS);
    if (!codec) {
        fprintf(stderr, "未找到Opus编码器\n");
        return 1;
    }

    stream = avformat_new_stream(fmt_ctx, NULL);
    if (!stream) goto end;

    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) goto end;

    // 设置Opus合规参数
    codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;
    codec_ctx->sample_rate = 48000;
    codec_ctx->channels = 2;
    codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
    codec_ctx->bit_rate = 128000; // 128kbps比特率

    // 全局头设置(适配Ogg封装)
    if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
        codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    // 打开编码器
    ret = avcodec_open2(codec_ctx, codec, NULL);
    if (ret < 0) goto end;

    // 将编码器参数复制到流
    ret = avcodec_parameters_from_context(stream->codecpar, codec_ctx);
    if (ret < 0) goto end;

    // 打开输出文件
    ret = avio_open(&fmt_ctx->pb, "output.opus", AVIO_FLAG_WRITE);
    if (ret < 0) goto end;

    // 写入文件头
    ret = avformat_write_header(fmt_ctx, NULL);
    if (ret < 0) goto end;

    // 分配帧内存
    frame = av_frame_alloc();
    if (!frame) goto end;
    frame->format = codec_ctx->sample_fmt;
    frame->sample_rate = codec_ctx->sample_rate;
    frame->channels = codec_ctx->channels;
    frame->channel_layout = codec_ctx->channel_layout;
    frame->nb_samples = nb_samples;

    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0) goto end;

    // 生成测试S16数据(替换为你的原始音频读取逻辑)
    for (i = 0; i < 100; i++) {
        ret = av_frame_make_writable(frame);
        if (ret < 0) goto end;

        samples = (uint16_t *)frame->data[0];
        for (j = 0; j < nb_samples * codec_ctx->channels; j++) {
            // 生成440Hz正弦波测试数据
            samples[j] = (uint16_t)(sin(2 * M_PI * j / 440) * 32767);
        }

        frame->pts = i * nb_samples;

        // 发送帧到编码器
        ret = avcodec_send_frame(codec_ctx, frame);
        if (ret < 0) goto end;

        // 接收编码后的数据包并写入文件
        while (ret >= 0) {
            ret = avcodec_receive_packet(codec_ctx, &pkt);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
            if (ret < 0) goto end;

            av_packet_rescale_ts(&pkt, codec_ctx->time_base, stream->time_base);
            pkt.stream_index = stream->index;

            ret = av_interleaved_write_frame(fmt_ctx, &pkt);
            av_packet_unref(&pkt);
            if (ret < 0) goto end;
        }
    }

    // 发送EOF信号,触发编码器输出剩余数据
    ret = avcodec_send_frame(codec_ctx, NULL);
    if (ret < 0) goto end;

    // 接收并写入剩余数据包
    while (ret >= 0) {
        ret = avcodec_receive_packet(codec_ctx, &pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
        if (ret < 0) goto end;

        av_packet_rescale_ts(&pkt, codec_ctx->time_base, stream->time_base);
        pkt.stream_index = stream->index;
        ret = av_interleaved_write_frame(fmt_ctx, &pkt);
        av_packet_unref(&pkt);
        if (ret < 0) goto end;
    }

    // 写入文件尾
    av_write_trailer(fmt_ctx);

end:
    // 资源释放
    if (codec_ctx) avcodec_free_context(&codec_ctx);
    if (frame) av_frame_free(&frame);
    if (fmt_ctx) {
        avio_closep(&fmt_ctx->pb);
        avformat_free_context(fmt_ctx);
    }
    if (ret < 0) {
        fprintf(stderr, "错误信息:%s\n", av_err2str(ret));
        return 1;
    }
    return 0;
}

内容的提问来源于stack exchange,提问作者Nyan Htet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 11:29:53