如何用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
相关产品推荐
相关产品推荐

