使用ffmpeg實現MP3toPCM
目錄
流程解析
1. 注冊協議、格式與編碼器
// 打開pcm文件
FILE * pcmFile = fopen(pcmPath, "wb+");
// 注冊解碼器
avcodec_register_all();
av_register_all();
2. 打開媒體源
// 首先為mp3文件分配一個AVFormatContext數據結構
mavFormatContext = avformat_alloc_context();
LOGI("open ac file %s...", fileString);
// 打開文件,並解析文件,然后填充AVFormatContext數據結構
// avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options);
// 1. AVFormatContext數據結構 2. mp3文件名 3. 指定AVInputFormat,設置為NULL則自動檢測 4.AVDictionary附加選項,一般設為NULL
int result = avformat_open_input(&mavFormatContext, fileString, NULL, NULL);
if (result != 0) {
LOGI("can't open file %s result %s", fileString, av_err2str(result));
return -1;
} else {
LOGI("open file %s success and result is %s", fileString, av_err2str(result));
}
// avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
// 讀取一部分視音頻數據並且獲得一些相關的信息
result = avformat_find_stream_info(mavFormatContext, NULL);
if (result < 0) {
LOGI("fail avformat avformat_find_stream_info %s result %s", fileString, av_err2str(result));
return -1;
} else {
LOGI("avformat_find_stream_info success result is %s", fileString, av_err2str(result));
}
3. 尋找各個流,並且打開對應的解碼器
// av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags)
// 獲取mp3文件中音頻對應的stream_index
// 1. AVFormatContext 2. 指定為查找音頻AVMEDIA_TYPE_AUDIO 3. wanted_stream_nb指定的stream號,-1為自動檢測 4. related_stream找相關的stream 5. decoder_ret如果不為NULL,則返回選擇的stream的decoder 6. 相關的flags 7. 返回值:返回相關的index
mstream_index = av_find_best_stream(mavFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
LOGI("stream_index is %d", mstream_index);
if (mstream_index == -1) {
LOGI("no audio stream");
return -1;
}
AVStream* audioStream = mavFormatContext->streams[mstream_index];
//if (audioStream->time_base.den && audioStream->time_base.num)
mavCodecContext = audioStream->codec;
LOGI("avCodecContext->codec_id is %d AV_CODEC_ID_AAC is %d", mavCodecContext->codec_id, AV_CODEC_ID_AAC);
// 找音頻流解碼器
AVCodec* avCodec = avcodec_find_decoder(mavCodecContext->codec_id);
if (avCodec == NULL) {
LOGI("Unsupported codec");
return -1;
}
// 打開音頻流解碼器
result = avcodec_open2(mavCodecContext, avCodec, NULL);
if (result < 0) {
LOGI("avcodec_open2 fail avformat_find_stream_info result is %s", av_err2str(result));
return -1;
} else {
LOGI("avcodec_open2 sucess avformat_find_stream_info result is %s", av_err2str(result));
}
4. 初始化解碼后的數據結構
if (!audioCodecIsSupported()) {
LOGI("because of audio Codec Is Not Supported so we will init swresampler...");
mswrContext = swr_alloc();
/*
struct SwrContext* swr_alloc_set_opts(
struct SwrContext * s, //如果為NULL則創建一個新的SwrContext,否則對已有的SwrContext進行參數設置
int64_t out_ch_layout, //輸出的聲道格式,AV_CH_LAYOUT_*,AV_CH_LAYOUT_STEREO(雙聲道)
enum AVSampleFormat out_sample_fmt, // 輸出的采樣格式,AV_SAMPLE_FMT_S16,16bit
int out_sample_rate, // 輸出的采樣率,44100
int64_t in_ch_layout, //輸入的聲道格式,
enum AVSampleFormat in_sample_fmt, // 輸入的采樣格式
int in_sample_rate, // 輸入的采樣率
int log_offset, // 日志相關
void * log_ctx // 日志相關
) */
mswrContext = swr_alloc_set_opts(mswrContext, av_get_default_channel_layout(OUT_PUT_CHANNELS), AV_SAMPLE_FMT_S16, 44100,
av_get_default_channel_layout(mavCodecContext->channels), mavCodecContext->sample_fmt, mavCodecContext->sample_rate, 0, NULL);
// 初始化上下文
if (!mswrContext || swr_init(mswrContext)) {
if (mswrContext)
swr_free(&mswrContext);
avcodec_close(mavCodecContext);
LOGI("init resampler failed...");
return -1;
}
}
5. 讀取流內容(packet),解碼(frame),重采樣(out_buffer),寫數據(fwrite)
// 讀取流內容到packet中
while (av_read_frame(mavFormatContext, &mpacket) >= 0) {
if (mpacket.stream_index == mstream_index) {
// avcodec_decode_audio4(AVCodecContext * avctx, AVFrame * frame, int * got_frame_ptr, const AVPacket * avpkt)
// 1. 解碼器 2. 輸出數據frame 3. 是否獲取到frame 4. 輸入數據packet
int len = avcodec_decode_audio4(mavCodecContext, mpAudioFrame, &gotframe, &mpacket);
if (len < 0) {
LOGI("decode audio error, skip packet");
return -1;
}
if (gotframe) {
// av_samples_get_buffer_size (int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align)
// 1. linesize 2. 通道數 3. 單個聲道的樣本數 4. 輸出采樣格式 5. 對齊
// 這個函數得到的結果是錯的
// AV_CH_LAYOUT_STEREO是3聲道,av_get_default_channel_layout(OUT_PUT_CHANNELS)為3
//int out_buffer_size=av_samples_get_buffer_size(NULL, AV_CH_LAYOUT_STEREO,
// mpAudioFrame->nb_samples, out_sample_fmt, 1);
// 每一份frame的樣本數 * 輸出pcm的通道數 * 樣本的采樣格式(16bit)
int out_buffer_size=mpAudioFrame->nb_samples *
OUT_PUT_CHANNELS *
av_get_bytes_per_sample(out_sample_fmt);
LOGI("mpAudioFrame->nb_samples = %d", mpAudioFrame->nb_samples);
int numChannels = OUT_PUT_CHANNELS;
int numFrames = 0;
void* audioData;
// 重新采樣
if (mswrContext) {
// swr_convert (struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in, int in_count)
// 1. SwrContext上下文 2. 輸出buffer 3. 輸出的單通道的樣本數 4. 輸入數據 5. 輸入的單通道的樣本數
numFrames = swr_convert(mswrContext, &out_buffer,
mpAudioFrame->nb_samples,
(const uint8_t **)mpAudioFrame->data,
mpAudioFrame->nb_samples);
if (numFrames < 0) {
LOGI("fail resample audio");
ret = -1;
break;
}
LOGI("index:%5d\t pts:%lld\t packet size:%d out_buffer_size: %d\n",index,mpacket.pts,mpacket.size, out_buffer_size);
//Write PCM
// 寫到pcm文件中
fwrite(out_buffer, 1, 4608, pcmFile);
index++;
av_packet_unref(&mpacket);
}
}
LOGI(" free");
}
}
6. 釋放資源
av_free(&mpacket); // 注意,這個函數只需調用一次,不然會出現段錯誤
LOGI("end");
av_free(mpAudioFrame);
swr_free(&mswrContext);
fclose(pcmFile);
av_free(out_buffer);
avcodec_close(mavCodecContext);
avformat_close_input(&mavFormatContext);
源碼
int AccompanyDecoder::init(const char *fileString, const char* pcmPath) {
LOGI("enter AccompanyDecoder::init");
maudioBuffer = NULL;
mposition = -1.0f;
maudioBufferCursor = 0;
maudioBufferSize = 0;
mswrContext = NULL;
mswrBuffer = NULL;
mswrBufferSize = 0;
uint8_t *out_buffer;
int index = 0;
AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16;
FILE * pcmFile = fopen(pcmPath, "wb+");
// 注冊解碼器
avcodec_register_all();
av_register_all();
mavFormatContext = avformat_alloc_context();
LOGI("open ac file %s...", fileString);
// 打開文件,並解析文件,然后填充avformat
int result = avformat_open_input(&mavFormatContext, fileString, NULL, NULL);
if (result != 0) {
LOGI("can't open file %s result %s", fileString, av_err2str(result));
return -1;
} else {
LOGI("open file %s success and result is %s", fileString, av_err2str(result));
}
// 檢查文件中的流信息
result = avformat_find_stream_info(mavFormatContext, NULL);
if (result < 0) {
LOGI("fail avformat avformat_find_stream_info %s result %s", fileString, av_err2str(result));
return -1;
} else {
LOGI("avformat_find_stream_info success result is %s", fileString, av_err2str(result));
}
mstream_index = av_find_best_stream(mavFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
LOGI("stream_index is %d", mstream_index);
if (mstream_index == -1) {
LOGI("no audio stream");
return -1;
}
AVStream* audioStream = mavFormatContext->streams[mstream_index];
//if (audioStream->time_base.den && audioStream->time_base.num)
mavCodecContext = audioStream->codec;
LOGI("avCodecContext->codec_id is %d AV_CODEC_ID_AAC is %d", mavCodecContext->codec_id, AV_CODEC_ID_AAC);
AVCodec* avCodec = avcodec_find_decoder(mavCodecContext->codec_id);
if (avCodec == NULL) {
LOGI("Unsupported codec");
return -1;
}
result = avcodec_open2(mavCodecContext, avCodec, NULL);
if (result < 0) {
LOGI("avcodec_open2 fail avformat_find_stream_info result is %s", av_err2str(result));
return -1;
} else {
LOGI("avcodec_open2 sucess avformat_find_stream_info result is %s", av_err2str(result));
}
if (!audioCodecIsSupported()) {
LOGI("because of audio Codec Is Not Supported so we will init swresampler...");
mswrContext = swr_alloc();
mswrContext = swr_alloc_set_opts(mswrContext, av_get_default_channel_layout(OUT_PUT_CHANNELS), AV_SAMPLE_FMT_S16, 44100,
av_get_default_channel_layout(mavCodecContext->channels), mavCodecContext->sample_fmt, mavCodecContext->sample_rate, 0, NULL);
if (!mswrContext || swr_init(mswrContext)) {
if (mswrContext)
swr_free(&mswrContext);
avcodec_close(mavCodecContext);
LOGI("init resampler failed...");
return -1;
}
}
LOGI("channels is %d sampleRate is %d", mavCodecContext->channels, mavCodecContext->sample_rate);
av_init_packet(&mpacket);
mpAudioFrame = av_frame_alloc();
int ret = 1;
av_init_packet(&mpacket);
int gotframe = 0;
out_buffer=(uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE*2);
while (av_read_frame(mavFormatContext, &mpacket) >= 0) {
if (mpacket.stream_index == mstream_index) {
int len = avcodec_decode_audio4(mavCodecContext, mpAudioFrame, &gotframe, &mpacket);
if (len < 0) {
LOGI("decode audio error, skip packet");
return -1;
}
if (gotframe) {
int out_buffer_size=av_samples_get_buffer_size(NULL, AV_CH_LAYOUT_STEREO,
mpAudioFrame->nb_samples, out_sample_fmt, 1);
LOGI("mpAudioFrame->nb_samples = %d", mpAudioFrame->nb_samples);
int numChannels = OUT_PUT_CHANNELS;
int numFrames = 0;
void* audioData;
// 重新采樣
if (mswrContext) {
numFrames = swr_convert(mswrContext, &out_buffer,
mpAudioFrame->nb_samples,
(const uint8_t **)mpAudioFrame->data,
mpAudioFrame->nb_samples);
if (numFrames < 0) {
LOGI("fail resample audio");
ret = -1;
break;
}
LOGI("index:%5d\t pts:%lld\t packet size:%d out_buffer_size: %d\n",index,mpacket.pts,mpacket.size, out_buffer_size);
//Write PCM
fwrite(out_buffer, 1, 4608, pcmFile);
index++;
av_packet_unref(&mpacket);
}
}
LOGI("free");
}
}
av_free(&mpacket);
LOGI("end");
av_free(mpAudioFrame);
swr_free(&mswrContext);
fclose(pcmFile);
av_free(out_buffer);
avcodec_close(mavCodecContext);
avformat_close_input(&mavFormatContext);
return 0;
}
參考
1. 源碼地址:https://github.com/mashenlyl/FFmpegDecoder