【音視頻進階開發指南筆記-2】使用ffmpeg庫解碼mp3文件


使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM