如何通过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类型采样值:
请求采样格式的时机错误
先创建空AVCodecContext并设置request_sample_fmt,但之后重新分配了新的上下文,之前的设置被覆盖。需在avcodec_parameters_to_context之后再设置请求格式。错误处理缺失
avcodec_send_packet和avcodec_receive_frame可能返回错误或需要多次调用(一个packet解码多个frame),未处理会导致数据不完整或错误。采样值读取逻辑错误
avFrame->linesize[0]是单通道字节数,不是采样点数,正确采样数是avFrame->nb_samples。- 直接按
sizeof(double)步进读取,未确认实际采样格式是否为AV_SAMPLE_FMT_DBL。 AudioFrameSample固定8元素数组,与实际立体声场景不匹配,输出多余无效数据。
资源泄漏
初始分配的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
相关产品推荐
相关产品推荐

