美文网首页
2. 使用ffmpeg库解码mp3文件

2. 使用ffmpeg库解码mp3文件

作者: _jetson_ | 来源:发表于2021-03-29 23:46 被阅读0次

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

相关文章

网友评论

      本文标题:2. 使用ffmpeg库解码mp3文件

      本文链接:https://www.haomeiwen.com/subject/iwbphltx.html