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

如何用FFmpeg/libav获取音频采样数据?解码帧无数据求助

FFmpeg解码音频时AVFrame无法获取解压数据的问题

我正在开发类DAW软件,需要实现音频波形可视化,核心是获取单个音频采样值并绘制。目前尝试读取首个采样值,但使用FFmpeg/libav解码时,无法将解压数据写入AVFrame。参照旧示例写的代码因为FFmpeg版本更新失效,调试发现avcodec_send_packet执行成功,但avcodec_receive_frame返回的Frame无数据。

当前代码逻辑:

  • 获取音频文件格式信息
  • 从格式中提取音频流信息
  • 验证流对应的编解码器为音频类型
  • 获取编解码器上下文
  • 创建空AVPacket和AVFrame(Packet存压缩数据,Frame存解压数据)
  • 读取音频文件首帧到Packet
  • 将Packet传入编解码器上下文解码
  • 尝试从编解码器上下文接收解压数据到Frame(此步骤失败)
  • 分配缓存准备存储采样值(暂未完成)

使用Qt开发的代码如下:

// Step 1 - get the formatting info of the audio file
AVFormatContext* format = avformat_alloc_context();
if (avformat_open_input(&format, path.toStdString().c_str(), NULL, NULL) != 0) {
    qDebug() << "Could not open file " << path;
    return -1;
}

// Step 2 - get audio stream info from the format
if (avformat_find_stream_info(format, NULL) < 0) {
    qDebug() << "Could not retrieve stream info from file " << path;
    return -1;
}

// Step 3 - check that the codec required for the stream is an audio codec
int stream_index = -1;
for (unsigned int i=0; i<format->nb_streams; i++) {
    if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        stream_index = i;
        break;
    }
}

if (stream_index == -1) {
    qDebug() << "Could not retrieve audio stream from file " << path;
    return -1;
}

// Step 4 -get the codec context
const AVCodec *codec = avcodec_find_decoder(format->streams[stream_index]->codecpar->codec_id);
AVCodecContext *codecContext = avcodec_alloc_context3(codec);
avcodec_open2(codecContext, codec, NULL);

// Step 5 - create an empty packet and frame to use
AVPacket *packet = av_packet_alloc();
AVFrame *frame = av_frame_alloc();

// Step 6 - the format reads the first frame from the audio file into our packet
av_read_frame(format, packet);
// Step 7 - pass that packet into the codec context to be decoded
avcodec_send_packet(codecContext, packet);
//Step 8 - pass our frame to the codec context to receive the uncompressed audio data of the first frame
avcodec_receive_frame(codecContext, frame);

// Step 9 - create a buffer to hold the values and try allocating samples to it from our frame
double *buffer;
av_samples_alloc((uint8_t**) &buffer, NULL, 1, frame->nb_samples, AV_SAMPLE_FMT_DBL, 0);
qDebug () << "packet: " << &packet;
qDebug() << "frame: " <<  frame;
qDebug () << "buffer: " << buffer;

问题根源及修正步骤

  1. 未拷贝编解码器参数到CodecContext
    FFmpeg新版本中,AVStream.codec已被废弃,改用codecpar存储参数。你只创建了CodecContext但未将stream->codecpar的参数拷贝进去,导致解码器缺少关键的音频配置(如采样率、通道数、采样格式等),无法正常解码。
    修正:在avcodec_alloc_context3后添加:

    if (avcodec_parameters_to_context(codecContext, format->streams[stream_index]->codecpar) < 0) {
        qDebug() << "Failed to copy codec parameters to context";
        return -1;
    }
    
  2. 未检查关键函数的返回值
    avcodec_send_packet和avcodec_receive_frame的返回值必须检查,它们可能返回EAGAIN(需要更多输入或输出)、AVERROR_EOF等状态,直接忽略会导致错误无法排查。
    修正示例:

    int send_ret = avcodec_send_packet(codecContext, packet);
    if (send_ret < 0) {
        qDebug() << "Failed to send packet for decoding: " << av_err2str(send_ret);
        return -1;
    }
    
    int recv_ret = avcodec_receive_frame(codecContext, frame);
    if (recv_ret == AVERROR(EAGAIN)) {
        qDebug() << "Need more packets to decode";
    } else if (recv_ret == AVERROR_EOF) {
        qDebug() << "End of stream";
    } else if (recv_ret < 0) {
        qDebug() << "Failed to receive frame: " << av_err2str(recv_ret);
        return -1;
    }
    
  3. 未过滤非音频流的Packet
    av_read_frame可能读到视频流或其他非音频流的Packet,必须检查packet->stream_index是否等于你找到的stream_index,否则解码无效数据。
    修正:读取Packet后添加过滤逻辑:

    while (av_read_frame(format, packet) >= 0) {
        if (packet->stream_index != stream_index) {
            av_packet_unref(packet);
            continue;
        }
        // 执行解码逻辑
        break; // 只处理第一个音频Packet
    }
    
  4. 资源初始化与释放的细节

    • avformat_alloc_context可以不用手动调用,avformat_open_input会自动分配,手动分配后如果打开失败会导致内存泄漏。
    • 使用完Packet和Frame后,必须调用av_packet_unref和av_frame_unref释放资源,避免内存泄漏。

修正后的核心代码片段

// Step 4 -get the codec context
const AVCodec *codec = avcodec_find_decoder(format->streams[stream_index]->codecpar->codec_id);
if (!codec) {
    qDebug() << "Unsupported codec";
    return -1;
}

AVCodecContext *codecContext = avcodec_alloc_context3(codec);
if (!codecContext) {
    qDebug() << "Failed to allocate codec context";
    return -1;
}

// 拷贝编解码器参数
if (avcodec_parameters_to_context(codecContext, format->streams[stream_index]->codecpar) < 0) {
    qDebug() << "Failed to copy codec parameters";
    avcodec_free_context(&codecContext);
    return -1;
}

if (avcodec_open2(codecContext, codec, NULL) < 0) {
    qDebug() << "Failed to open codec";
    avcodec_free_context(&codecContext);
    return -1;
}

// Step 5 - create an empty packet and frame to use
AVPacket *packet = av_packet_alloc();
AVFrame *frame = av_frame_alloc();

// Step 6 - 读取第一个音频Packet
int read_ret;
while ((read_ret = av_read_frame(format, packet)) >= 0) {
    if (packet->stream_index != stream_index) {
        av_packet_unref(packet);
        continue;
    }
    break;
}
if (read_ret < 0) {
    qDebug() << "Failed to read audio packet";
    return -1;
}

// Step 7 - 发送Packet解码
int send_ret = avcodec_send_packet(codecContext, packet);
if (send_ret < 0) {
    qDebug() << "Send packet failed: " << av_err2str(send_ret);
    av_packet_unref(packet);
    return -1;
}

// Step 8 - 接收解码后的Frame
int recv_ret = avcodec_receive_frame(codecContext, frame);
if (recv_ret < 0) {
    qDebug() << "Receive frame failed: " << av_err2str(recv_ret);
    av_packet_unref(packet);
    return -1;
}

// 此时frame已包含有效解码数据,可以访问frame->data、frame->nb_samples等字段
qDebug() << "Decoded frame has " << frame->nb_samples << " samples";

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 21:10:52