MacOS下FFmpeg C API无法初始化WMAV2解析器但CLI可用
WMAV2解码器解析器初始化失败问题
我参考FFmpeg的decode_audio.c示例编写代码,但无法为AV_CODEC_ID_WMAV2初始化解析器,测试代码如下:
#include <stdio.h> #include <libavcodec/avcodec.h> int main() { // 能成功找到解码器 const AVCodec * codec = avcodec_find_decoder(AV_CODEC_ID_WMAV2); if (!codec) { fprintf(stderr, "codec not found\n"); return 1; } // 解析器始终返回NULL AVCodecParserContext * parser = av_parser_init(codec->id); if (!parser) { fprintf(stderr, "parser not found\n"); return 1; } av_parser_close(parser); return 0; }
编译命令:
clang -c -I/opt/homebrew/Cellar/ffmpeg/6.0_1/include wma2mp3.c -o obj/wma2mp3.o clang -L/opt/homebrew/Cellar/ffmpeg/6.0_1/lib -lavcodec obj/wma2mp3.o -o wma2mp3
但同一台机器上的FFmpeg CLI却能正常转换WMA文件:
% ffmpeg -i test.wma test.mp3 ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers built with Apple clang version 14.0.3 (clang-1403.0.22.14.1) configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/6.0_1 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-audiotoolbox --enable-neon libavutil 58. 2.100 / 58. 2.100 libavcodec 60. 3.100 / 60. 3.100 libavformat 60. 3.100 / 60. 3.100 libavdevice 60. 1.100 / 60. 1.100 libavfilter 9. 3.100 / 9. 3.100 libswscale 7. 1.100 / 7. 1.100 libswresample 4. 10.100 / 4. 10.100 libpostproc 57. 1.100 / 57. 1.100 Guessed Channel Layout for Input Stream #0.0 : mono Input #0, asf, from 'test.wma': Metadata: ToolName : Windows Media Encoding Utility ToolVersion : 8.00.00.0343 Duration: 00:00:00.74, start: 0.000000, bitrate: 80 kb/s Stream #0:0: Audio: wmav2 (a[1][0][0] / 0x0161), 44100 Hz, 1 channels, fltp, 48 kb/s Stream mapping: Stream #0:0 -> #0:0 (wmav2 (native) -> mp3 (libmp3lame)) Press [q] to stop, [?] for help Output #0, mp3, to 'test.mp3': Metadata: ToolName : Windows Media Encoding Utility ToolVersion : 8.00.00.0343 TSSE : Lavf60.3.100 Stream #0:0: Audio: mp3, 44100 Hz, mono, fltp Metadata: encoder : Lavc60.3.100 libmp3lame [libmp3lame @ 0x130706320] Queue input is backward in timeed=N/A [mp3 @ 0x1307056e0] Application provided invalid, non monotonically increasing dts to muxer in stream 0: 15668 >= 14764 size= 8kB time=00:00:00.97 bitrate= 65.8kbits/s speed= 103x video:0kB audio:8kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 4.048112%
设备环境:Apple M1,MacOS 13.5.2 (22G91)
想问两个问题:
- FFmpeg CLI是不是使用了
av_parser_parse2以外的机制来完成转换? - 用C API实现WMA转MP3的正确方式是什么?
问题解答
1. 解析器初始化失败的原因
FFmpeg中WMAV2没有对应的编解码器解析器,所以av_parser_init(AV_CODEC_ID_WMAV2)返回NULL是正常行为。解析器仅用于将无容器的裸编码流分割成单个编码包,并非所有编解码器都需要或提供解析器。
2. FFmpeg CLI正常工作的机制
CLI并没有使用解析器处理你的WMA文件——因为你的文件是ASF容器封装的WMAV2音频。CLI通过avformat_open_input打开文件,利用AVFormatContext处理容器格式,ASF解复用器会直接输出分割好的完整编码包给解码器,不需要额外解析器拆分数据。
只有处理裸WMAV2流(比如网络传输的无容器编码数据)时才需要解析器,这种场景非常少见。
3. C API实现WMA转MP3的正确方式
正确做法是使用FFmpeg的解复用-解码-重采样-编码-复用完整流程,而非手动初始化解析器。核心步骤及示例代码如下:
核心流程
- 用
avformat_open_input打开输入文件,获取格式上下文 - 定位音频流,初始化解码器上下文并打开解码器
- 创建输出格式上下文,初始化MP3编码器上下文并打开编码器
- 循环读取输入包,发送给解码器获取原始音频帧
- 对原始帧重采样(匹配编码器要求的格式),发送给编码器获取MP3包
- 将MP3包写入输出文件
示例代码
#include <stdio.h> #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswresample/swresample.h> int main() { AVFormatContext *input_fmt_ctx = NULL; AVFormatContext *output_fmt_ctx = NULL; const AVCodec *decoder = NULL; const AVCodec *encoder = NULL; AVCodecContext *dec_ctx = NULL; AVCodecContext *enc_ctx = NULL; int audio_stream_idx = -1; AVPacket pkt; AVFrame *frame = NULL; AVFrame *frame_resampled = NULL; SwrContext *swr_ctx = NULL; // 初始化FFmpeg组件 av_register_all(); avformat_network_init(); // 打开输入文件 if (avformat_open_input(&input_fmt_ctx, "test.wma", NULL, NULL) != 0) { fprintf(stderr, "无法打开输入文件\n"); return 1; } if (avformat_find_stream_info(input_fmt_ctx, NULL) < 0) { fprintf(stderr, "无法获取流信息\n"); return 1; } // 找到音频流 for (int i = 0; i < input_fmt_ctx->nb_streams; i++) { if (input_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audio_stream_idx = i; decoder = avcodec_find_decoder(input_fmt_ctx->streams[i]->codecpar->codec_id); if (!decoder) { fprintf(stderr, "找不到解码器\n"); return 1; } break; } } if (audio_stream_idx == -1) { fprintf(stderr, "未找到音频流\n"); return 1; } // 初始化解码器上下文 dec_ctx = avcodec_alloc_context3(decoder); avcodec_parameters_to_context(dec_ctx, input_fmt_ctx->streams[audio_stream_idx]->codecpar); if (avcodec_open2(dec_ctx, decoder, NULL) < 0) { fprintf(stderr, "无法打开解码器\n"); return 1; } // 初始化输出格式和编码器 avformat_alloc_output_context2(&output_fmt_ctx, NULL, "mp3", "test.mp3"); if (!output_fmt_ctx) { fprintf(stderr, "无法初始化输出上下文\n"); return 1; } encoder = avcodec_find_encoder(AV_CODEC_ID_MP3); if (!encoder) { fprintf(stderr, "找不到MP3编码器\n"); return 1; } AVStream *out_stream = avformat_new_stream(output_fmt_ctx, encoder); enc_ctx = avcodec_alloc_context3(encoder); // 设置编码器参数 enc_ctx->sample_rate = dec_ctx->sample_rate; enc_ctx->channel_layout = dec_ctx->channel_layout; enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout); enc_ctx->sample_fmt = encoder->sample_fmts[0]; enc_ctx->bit_rate = 64000; if (output_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } if (avcodec_open2(enc_ctx, encoder, NULL) < 0) { fprintf(stderr, "无法打开编码器\n"); return 1; } avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); // 打开输出文件 if (!(output_fmt_ctx->oformat->flags & AVFMT_NOFILE)) { if (avio_open(&output_fmt_ctx->pb, "test.mp3", AVIO_FLAG_WRITE) < 0) { fprintf(stderr, "无法打开输出文件\n"); return 1; } } if (avformat_write_header(output_fmt_ctx, NULL) < 0) { fprintf(stderr, "无法写入文件头\n"); return 1; } // 初始化重采样上下文 swr_ctx = swr_alloc_set_opts(NULL, enc_ctx->channel_layout, enc_ctx->sample_fmt, enc_ctx->sample_rate, dec_ctx->channel_layout, dec_ctx->sample_fmt, dec_ctx->sample_rate, 0, NULL); swr_init(swr_ctx); frame = av_frame_alloc(); frame_resampled = av_frame_alloc(); frame_resampled->sample_rate = enc_ctx->sample_rate; frame_resampled->channel_layout = enc_ctx->channel_layout; frame_resampled->format = enc_ctx->sample_fmt; // 循环处理数据包 while (av_read_frame(input_fmt_ctx, &pkt) >= 0) { if (pkt.stream_index == audio_stream_idx) { if (avcodec_send_packet(dec_ctx, &pkt) == 0) { while (avcodec_receive_frame(dec_ctx, frame) == 0) { // 重采样音频帧 av_frame_get_buffer(frame_resampled, 0); swr_convert(swr_ctx, frame_resampled->data, frame_resampled->nb_samples, (const uint8_t **)frame->data, frame->nb_samples); frame_resampled->nb_samples = swr_get_delay(swr_ctx, dec_ctx->sample_rate) + frame->nb_samples; // 发送帧给编码器 if (avcodec_send_frame(enc_ctx, frame_resampled) == 0) { AVPacket enc_pkt = {0}; av_init_packet(&enc_pkt); while (avcodec_receive_packet(enc_ctx, &enc_pkt) == 0) { enc_pkt.stream_index = out_stream->index; av_interleaved_write_frame(output_fmt_ctx, &enc_pkt); av_packet_unref(&enc_pkt); } } } } } av_packet_unref(&pkt); } // 刷新编码器 avcodec_send_frame(enc_ctx, NULL); AVPacket enc_pkt = {0}; av_init_packet(&enc_pkt); while (avcodec_receive_packet(enc_ctx, &enc_pkt) == 0) { enc_pkt.stream_index = out_stream->index; av_interleaved_write_frame(output_fmt_ctx, &enc_pkt); av_packet_unref(&enc_pkt); } // 写入文件尾 av_write_trailer(output_fmt_ctx); // 释放资源 avcodec_close(dec_ctx); avcodec_close(enc_ctx); avformat_close_input(&input_fmt_ctx); if (!(output_fmt_ctx->oformat->flags & AVFMT_NOFILE)) { avio_close(output_fmt_ctx->pb); } avformat_free_context(output_fmt_ctx); av_frame_free(&frame); av_frame_free(&frame_resampled); swr_free(&swr_ctx); return 0; }
编译命令
需要链接更多FFmpeg库:
clang -c -I/opt/homebrew/Cellar/ffmpeg/6.0_1/include wma2mp3.c -o obj/wma2mp3.o clang -L/opt/homebrew/Cellar/ffmpeg/6.0_1/lib -lavformat -lavcodec -lavutil -lswresample obj/wma2mp3.o -o wma2mp3
内容的提问来源于stack exchange,提问作者grendell
相关产品推荐
相关产品推荐

