使用LibAV API将MP3文件转码为G.711编码WAV文件的方案咨询
流程评审结论
你规划的三步转码逻辑是完全合理的,没有方向性误解:
- 第一步解码MP3得到原始PCM数据
- 第二步将PCM编码为G.711 μ-law格式
- 第三步将编码后的数据封装进WAV容器,整体流程符合LibAV转码的标准逻辑
现有简化代码需要修正的问题
你省略了几个旧版LibAV转码必须的关键步骤,直接运行会出现编码失败、音频杂音、内存泄漏、丢尾帧等问题:
- 缺失输入流信息解析步骤
avformat_open_input仅打开文件头,必须调用avformat_find_stream_info解析流参数,还要通过av_find_best_stream找到音频流索引,过滤非音频包,否则解码会直接报错。 - 缺失重采样适配步骤
MP3解码输出的PCM通常是44.1kHz/48kHz、双通道、S16格式,而G.711编码强制要求8kHz采样率、单通道、16位整型输入,必须通过swr_convert做重采样格式转换,否则编码输出的音频会是杂音。 - 缺失
gotFrame有效性判断avcodec_decode_audio4和avcodec_encode_audio2都不是每次调用都会输出有效帧/包,只有当gotFrame返回1时,才能进行下一步处理,否则会传入空指针导致崩溃。 - 缺失包内存释放逻辑
旧版LibAV中av_read_frame拿到的输入包、编码生成的输出包,使用完必须调用av_packet_unref释放引用,否则在嵌入式ARM设备上会出现内存泄漏,长时间运行触发OOM。 - 缺失编码器缓存冲刷步骤
所有输入包处理完成后,需要给编码器传入NULL帧,冲刷编码器内部缓存的剩余数据,否则会丢失最后几百毫秒的音频。 - 输出上下文参数需要严格对齐
初始化WAV输出容器时,音频流的采样率、通道数、编码格式必须和G.711编码器参数完全一致,否则avformat_write_header会调用失败。
适配旧版LibAV的修正后核心流程示例
// 1. 打开输入MP3,解析流信息 AVFormatContext *ifmt_ctx = NULL; avformat_open_input(&ifmt_ctx, sMp3FileName, NULL, NULL); avformat_find_stream_info(ifmt_ctx, NULL); // 绑定音频解码器 int audio_stream_idx = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &decoder, 0); AVCodecContext *dec_ctx = avcodec_alloc_context3(decoder); avcodec_parameters_to_context(dec_ctx, ifmt_ctx->streams[audio_stream_idx]->codecpar); avcodec_open2(dec_ctx, decoder, NULL); // 2. 初始化重采样上下文,适配G.711输入要求 SwrContext *swr_ctx = swr_alloc_set_opts(NULL, av_get_default_channel_layout(1), AV_SAMPLE_FMT_S16, 8000, // 输出:单通道、S16、8kHz av_get_default_channel_layout(dec_ctx->channels), dec_ctx->sample_fmt, dec_ctx->sample_rate, // 输入:MP3解码参数 0, NULL, NULL); swr_init(swr_ctx); // 3. 初始化G.711 μ-law编码器 AVCodec *encoder = avcodec_find_encoder(CODEC_ID_PCM_MULAW); AVCodecContext *enc_ctx = avcodec_alloc_context3(encoder); enc_ctx->sample_rate = 8000; enc_ctx->channels = 1; enc_ctx->sample_fmt = AV_SAMPLE_FMT_S16; enc_ctx->bit_rate = 64000; // G.711固定码率:8kHz * 8bit = 64kbps avcodec_open2(enc_ctx, encoder, NULL); // 4. 初始化WAV输出容器 AVFormatContext *ofmt_ctx = NULL; avformat_alloc_output_context2(&ofmt_ctx, NULL, "wav", sWavFileName); AVStream *out_stream = avformat_new_stream(ofmt_ctx, encoder); avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); avio_open(&ofmt_ctx->pb, sWavFileName, AVIO_FLAG_WRITE); avformat_write_header(ofmt_ctx, NULL); // 转码主循环 AVPacket in_pkt, out_pkt; AVFrame *dec_frame = av_frame_alloc(), *resample_frame = av_frame_alloc(); resample_frame->format = AV_SAMPLE_FMT_S16; resample_frame->channels = 1; resample_frame->sample_rate = 8000; while (av_read_frame(ifmt_ctx, &in_pkt) >= 0) { if (in_pkt.stream_index != audio_stream_idx) { av_packet_unref(&in_pkt); continue; } // 解码MP3包 int got_dec = 0; avcodec_decode_audio4(dec_ctx, dec_frame, &got_dec, &in_pkt); av_packet_unref(&in_pkt); if (!got_dec) continue; // 重采样到G.711要求的格式 resample_frame->nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, dec_ctx->sample_rate) + dec_frame->nb_samples, 8000, dec_ctx->sample_rate, AV_ROUND_UP); av_frame_get_buffer(resample_frame, 0); swr_convert(swr_ctx, resample_frame->data, resample_frame->nb_samples, (const uint8_t **)dec_frame->data, dec_frame->nb_samples); // 编码为G.711 int got_enc = 0; av_init_packet(&out_pkt); avcodec_encode_audio2(enc_ctx, &out_pkt, resample_frame, &got_enc); if (got_enc) { av_interleaved_write_frame(ofmt_ctx, &out_pkt); av_packet_unref(&out_pkt); } } // 冲刷编码器剩余缓存 int got_enc = 0; av_init_packet(&out_pkt); avcodec_encode_audio2(enc_ctx, &out_pkt, NULL, &got_enc); if (got_enc) { av_interleaved_write_frame(ofmt_ctx, &out_pkt); av_packet_unref(&out_pkt); } av_write_trailer(ofmt_ctx); // 后续释放所有申请的资源即可
内容的提问来源于stack exchange,提问作者Steeve007
相关产品推荐
相关产品推荐

