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

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)

想问两个问题:

  1. FFmpeg CLI是不是使用了av_parser_parse2以外的机制来完成转换?
  2. 用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 06:30:56