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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容