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

如何通过libavcodec从AVFrame中提取double类型音频采样值?

问题

使用libavcodec提取音频采样时,无法正确从AVFrame中提取double类型的音频采样值。Python通过PyAV库编写的脚本可以正常运行,但自行实现的C++程序输出无效数据。

可正常运行的Python脚本

import av

input_file = 'C:\\path\\to\\sound.mp3'
container = av.open(input_file)

# Find audio stream
audio_stream = None
for stream in container.streams:
    if stream.type == 'audio':
        audio_stream = stream
        break

if audio_stream is None:
    print("No audio streams detected")
    container.close()
    exit(1)

# Access audio samples
sampleListL = []
sampleListR = []
for packet in container.demux(audio_stream):
    for frame in packet.decode():
        sampleListL += frame.to_ndarray()[0].tolist()
        sampleListR += frame.to_ndarray()[1].tolist()

print("Channel L,Channel R")
for s in zip(sampleListL, sampleListR):
    print(str.format("{},{}",s[0],s[1]))

# Cleanup
container.close()

输出无效数据的C++程序

// avcodec-simple-frame-extraction.cpp : Defines the entry point for the application.

#include "avcodec-simple-frame-extraction.h"
#include <array>
#include <limits>

using namespace std;

using AudioFrameSample = std::array<double, 8>;

static int ReadFramesForAudioFile(const char* filepath)
{
    AVCodecContext* avCodecCtx = avcodec_alloc_context3(nullptr);
    AVFormatContext* avFmtCtx = avformat_alloc_context();
    AVStream* avFirstAudioStream = nullptr;
    
    AVCodecParameters* avCodecParams = nullptr;
    if (avformat_open_input(&avFmtCtx, filepath, nullptr, nullptr) != 0) {
        fprintf(stderr, "Couldn't open file with avformat_open_input\n");
        return 1;
    }
    if (avformat_find_stream_info(avFmtCtx, nullptr) < 0) {
        fprintf(stderr, "Couldn't get stream info with avformat_find_stream_info\n");
        return 1;
    }
    avCodecCtx->request_sample_fmt = AV_SAMPLE_FMT_DBL;
    //find the index of the first audio stream
    int streamIndex = -1;
    for (int si = 0; si < avFmtCtx->nb_streams; si++) {
        if (avFmtCtx->streams[si]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            avCodecParams = avFmtCtx->streams[si]->codecpar;
            streamIndex = si;
            break;
        }
    }
    //find decoder codec for local stream
    const AVCodec* avCodec = avcodec_find_decoder(avCodecParams->codec_id);
    avCodecCtx = avcodec_alloc_context3(avCodec);
    avcodec_parameters_to_context(avCodecCtx, avCodecParams);
    avcodec_open2(avCodecCtx, avCodec, nullptr);
    if (streamIndex == -1) {
        fprintf(stderr, "No audio streams detected\n");
    }
    avFirstAudioStream = avFmtCtx->streams[streamIndex];
    //no resampling, just use the same sampling rate as the original codec
    //prepare reading data
    AVPacket* avPacket = av_packet_alloc();
    AVFrame* avFrame = av_frame_alloc();
    if (avFrame == nullptr) {
        fprintf(stderr, "Error allocating the frame\n");
        return 1;
    }
    std::vector<AudioFrameSample> mySamples;
    while (av_read_frame(avFmtCtx, avPacket) >= 0) {
        avcodec_send_packet(avCodecCtx, avPacket);
        avcodec_receive_frame(avCodecCtx, avFrame);
        //TODO: Study and use decoding technique in PyAV

        for (int ch = 0; ch < avFrame->ch_layout.nb_channels; ch++) {
            for (int s = 0; s < avFrame->linesize[0]; s+=sizeof(double)) {
                double value;
                memcpy(&value, &avFrame->data[ch][s], sizeof(double));
                mySamples.push_back(AudioFrameSample{ value });
            }
        }
        av_packet_unref(avPacket);
    }
    for (auto& f : mySamples) {
        for (int ch = 0; ch < 6; ch++) {
            printf("%0.8f,", f[ch]);
        }
        printf("%0.8f\n", f[7]);
    }
    avcodec_free_context(&avCodecCtx);
    av_packet_free(&avPacket);
    av_frame_free(&avFrame);
    return 0;
}

int main(int argc, char* argv[])
{
    if (argc == 2) {
        ReadFramesForAudioFile(argv[1]);
    }
    else {
        fprintf(stderr, "Usage: avcodec-simple-frame-extraction [path]\n");
        return 1;
    }
    return 0;
}

需要找出C++代码中的问题并给出正确实现方法。


解决方法

你的C++代码存在几个关键问题,导致无法正确提取double类型采样值:

  1. 请求采样格式的时机错误
    先创建空AVCodecContext并设置request_sample_fmt,但之后重新分配了新的上下文,之前的设置被覆盖。需在avcodec_parameters_to_context之后再设置请求格式。

  2. 错误处理缺失
    avcodec_send_packet和avcodec_receive_frame可能返回错误或需要多次调用(一个packet解码多个frame),未处理会导致数据不完整或错误。

  3. 采样值读取逻辑错误

    • avFrame->linesize[0]是单通道字节数,不是采样点数,正确采样数是avFrame->nb_samples。
    • 直接按sizeof(double)步进读取,未确认实际采样格式是否为AV_SAMPLE_FMT_DBL。
    • AudioFrameSample固定8元素数组,与实际立体声场景不匹配,输出多余无效数据。
  4. 资源泄漏
    初始分配的avCodecCtx未释放,造成内存泄漏。

修正后的C++代码

#include "avcodec-simple-frame-extraction.h"
#include <array>
#include <vector>
#include <cstdio>

using namespace std;

// 立体声采样,对应Python代码的左右声道
using AudioSamplePair = array<double, 2>;

static int ReadFramesForAudioFile(const char* filepath)
{
    AVFormatContext* avFmtCtx = nullptr;
    if (avformat_open_input(&avFmtCtx, filepath, nullptr, nullptr) != 0) {
        fprintf(stderr, "Couldn't open input file\n");
        return 1;
    }

    if (avformat_find_stream_info(avFmtCtx, nullptr) < 0) {
        fprintf(stderr, "Couldn't retrieve stream info\n");
        avformat_close_input(&avFmtCtx);
        return 1;
    }

    // 查找第一个音频流
    int streamIndex = -1;
    AVCodecParameters* avCodecParams = nullptr;
    for (int si = 0; si < avFmtCtx->nb_streams; si++) {
        if (avFmtCtx->streams[si]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            avCodecParams = avFmtCtx->streams[si]->codecpar;
            streamIndex = si;
            break;
        }
    }

    if (streamIndex == -1) {
        fprintf(stderr, "No audio streams detected\n");
        avformat_close_input(&avFmtCtx);
        return 1;
    }

    // 初始化解码器上下文
    const AVCodec* avCodec = avcodec_find_decoder(avCodecParams->codec_id);
    if (!avCodec) {
        fprintf(stderr, "Unsupported codec\n");
        avformat_close_input(&avFmtCtx);
        return 1;
    }

    AVCodecContext* avCodecCtx = avcodec_alloc_context3(avCodec);
    if (!avCodecCtx) {
        fprintf(stderr, "Failed to allocate codec context\n");
        avformat_close_input(&avFmtCtx);
        return 1;
    }

    // 复制参数并设置请求的采样格式
    if (avcodec_parameters_to_context(avCodecCtx, avCodecParams) < 0) {
        fprintf(stderr, "Failed to copy codec parameters\n");
        avcodec_free_context(&avCodecCtx);
        avformat_close_input(&avFmtCtx);
        return 1;
    }

    avCodecCtx->request_sample_fmt = AV_SAMPLE_FMT_DBL;

    if (avcodec_open2(avCodecCtx, avCodec, nullptr) < 0) {
        fprintf(stderr, "Failed to open codec\n");
        avcodec_free_context(&avCodecCtx);
        avformat_close_input(&avFmtCtx);
        return 1;
    }

    AVPacket* avPacket = av_packet_alloc();
    AVFrame* avFrame = av_frame_alloc();
    if (!avPacket || !avFrame) {
        fprintf(stderr, "Failed to allocate packet or frame\n");
        av_frame_free(&avFrame);
        av_packet_free(&avPacket);
        avcodec_free_context(&avCodecCtx);
        avformat_close_input(&avFmtCtx);
        return 1;
    }

    vector<AudioSamplePair> samples;

    // 读取数据包并解码
    while (av_read_frame(avFmtCtx, avPacket) >= 0) {
        if (avPacket->stream_index != streamIndex) {
            av_packet_unref(avPacket);
            continue;
        }

        int ret = avcodec_send_packet(avCodecCtx, avPacket);
        if (ret < 0) {
            fprintf(stderr, "Error sending packet to decoder\n");
            av_packet_unref(avPacket);
            continue;
        }

        // 循环接收所有解码后的帧
        while (ret >= 0) {
            ret = avcodec_receive_frame(avCodecCtx, avFrame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret < 0) {
                fprintf(stderr, "Error receiving frame from decoder\n");
                break;
            }

            // 确认采样格式是double
            if (avFrame->format != AV_SAMPLE_FMT_DBL) {
                fprintf(stderr, "Decoder didn't output double format samples\n");
                av_frame_unref(avFrame);
                continue;
            }

            // 读取立体声采样
            if (avFrame->ch_layout.nb_channels >= 2) {
                double* chL = reinterpret_cast<double*>(avFrame->data[0]);
                double* chR = reinterpret_cast<double*>(avFrame->data[1]);
                for (int i = 0; i < avFrame->nb_samples; i++) {
                    samples.push_back({chL[i], chR[i]});
                }
            }

            av_frame_unref(avFrame);
        }

        av_packet_unref(avPacket);
    }

    // 输出结果
    printf("Channel L,Channel R\n");
    for (const auto& sample : samples) {
        printf("%.8f,%.8f\n", sample[0], sample[1]);
    }

    // 释放资源
    av_frame_free(&avFrame);
    av_packet_free(&avPacket);
    avcodec_free_context(&avCodecCtx);
    avformat_close_input(&avFmtCtx);

    return 0;
}

int main(int argc, char* argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: avcodec-simple-frame-extraction [path]\n");
        return 1;
    }

    return ReadFramesForAudioFile(argv[1]);
}

关键改进点说明

  • 正确设置采样格式请求:在avcodec_parameters_to_context之后设置request_sample_fmt,确保解码器收到正确格式要求。
  • 完善错误处理:处理avcodec_send_packet和avcodec_receive_frame的返回值,支持一个packet解码多个frame的场景。
  • 正确读取采样值:通过avFrame->nb_samples获取采样点数,直接将data指针转换为double*访问,避免错误的内存复制和步进逻辑。
  • 匹配立体声场景:使用AudioSamplePair存储左右声道采样,输出格式与Python代码一致。
  • 完整资源释放:确保所有FFmpeg分配的资源都被正确释放,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 22:26:59