分类 Codec 下的文章

1. 解码 mp3 的时候,使用 ffprobe,当 ffmpeg 的版本不同,出来的结果也不一样。

在 早期版本的时候,显示 sample format 是 s16 在 3.X 版本的时候,显示是 S16P 在 4.X 版本的时候,显示是 FLTP

经过实际测试,这个只是在解码的时候,写入 PCM 的格式。而且和 ffmpeg 本身的 build 相关。 当我们在代码中循环打印 支持的 格式的时候:

printf("support formats: %d \n", *(codec->sample_fmts + i));  

这个 codec 对应的是 AVCodec,它的 sample_fmts 对应的是 const enum AVSampleFormat *sample_fmts; AVSampleFormat 的 定义是在 libavutil/samplefmt.h 中。

enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar
    AV_SAMPLE_FMT_S64,         ///< signed 64 bits
    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};

AVCodec 的定义在 libavcodec/avcodec.h 中。 codecpar 是在 AVStream 中定义的,AVCodecParameters *codecpar; AVStream 是在 libavformat/avformat.h 中。 发现 3.X 版本支持的是 1 和 6,对应到 S16 和 S16P, 而 4.X 版本支持的是 3 和 8.对应到 FLT 和 FLTP。

所以使用 3.X 版本解压出来的 PCM 的播放指令是:

play -t raw -r 48k -e signed-integer -b 16 -c 2 test.pcm

使用 4.X 版本解压出来的 PCM 的播放指令是:

play -t raw -r 48k -e floating-point -b 32 -c 2 ./data_decode/out.pcm

参考: https://trac.ffmpeg.org/ticket/7321 https://stackoverflow.com/questions/35226255/audio-sample-format-s16p-ffmpeg-or-audio-codec-bug https://bbs.csdn.net/topics/391984409

2. ffprobe 的用法可以参考:

https://my.oschina.net/u/4324861/blog/4325767 https://blog.csdn.net/byc6352/article/details/96729348 https://www.cnblogs.com/renhui/p/9209664.html

3. mp3 的 文件格式 和 编码,参考:

https://www.cnblogs.com/ranson7zop/p/7655474.html https://blog.csdn.net/xiahouzuoxin/article/details/7849249

4. API 变更记录

https://blog.csdn.net/leixiaohua1020/article/details/41013567

5. 使用 lame 编码 mp3 ,可以参考:

https://www.jianshu.com/p/dce4e2e9ed75 https://blog.csdn.net/bjrxyz/article/details/73435407 https://blog.csdn.net/rrrfff/article/details/18701885?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param https://blog.csdn.net/gonner_2011/article/details/77183947?utm_medium=distribute.pc_relevant.none-task-blog-title-2&spm=1001.2101.3001.4242 https://blog.csdn.net/jody1989/article/details/75642579 https://blog.csdn.net/ssllkkyyaa/article/details/90400302

6. 编码 mp3 除了 lame 还可以考虑 libshine,参考:

http://zhgeaits.me/android/2016/06/17/android-ffmpeg.html

7. 旧版本的 avcodec_decode_audio4 被废弃了,需要用收发机制。

旧版本的写法:

        const char * infile = IN_FILE;
        const char * outfile = OUT_FILE;

        //注册所有容器解码器
        av_register_all();
        //printf("the video file is %s\n",argv[1]);
        AVFormatContext * fmt_ctx = avformat_alloc_context();

        //打开文件
        if (avformat_open_input(&fmt_ctx, infile , NULL, NULL) < 0) {
                printf("open file error");
                return -1;
        }

        //读取音频格式文件信息
        if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
                printf("find stream info error");
                return -1;
        }
        // 打印出解析到的媒体信息
        av_dump_format(fmt_ctx, 0, infile, 0);

        //获取音频索引
        int audio_stream_index = -1;
        for (int i = 0; i < fmt_ctx->nb_streams; i++) {
                if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
                        audio_stream_index = i;
                        printf("find audio stream index\n");
                        break;
                }
        }
        if (audio_stream_index == -1) {
                printf("did not find a audio stream\n");
                return -1;
        }
        //获取解码器
        AVCodecContext *codec_ctx = avcodec_alloc_context3(NULL);
        avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[audio_stream_index]->codecpar);
        AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id);
        if (codec == NULL) {
                printf("unsupported codec !\n");
                return -1;
        }

        //打开解码器
        if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
                printf("could not open codec");
                return -1;
        }

        //分配AVPacket和AVFrame内存,用于接收音频数据,解码数据
        AVPacket *packet = av_packet_alloc();
        AVFrame *frame = av_frame_alloc();
        //接收解码结果
        int got_frame;
        int index = 0;

        //pcm输出文件
        FILE *out_file = fopen(outfile, "wb");
        //将音频数据读入packet
        while (av_read_frame(fmt_ctx, packet) == 0) {
                //取音频索引packet
                if (packet->stream_index == audio_stream_index) {
                        //将packet解码成AVFrame
                        if (avcodec_decode_audio4(codec_ctx, frame, &got_frame, packet) < 0) {
                                printf("decode error:%d", index);
                                break;
                        }
                        if (got_frame > 0) {
                                //printf("decode frame:%d", index++);
                                //想将单个声道pcm数据写入文件
                                fwrite(frame->data[0], 1, static_cast<size_t>(frame->linesize[0]), out_file);
                        }
                }
        }
        printf("decode finish...");

        //释放资源
        av_packet_unref(packet);
        av_frame_free(&frame);
        avcodec_close(codec_ctx);
        avformat_close_input(&fmt_ctx);
        fclose(out_file);
}

新版本的解码这样写:

void decode(const char * infile, const char * outfile)
{
    AVFormatContext * fmt_ctx = 0;  // ffmpeg的全局上下文,所有ffmpeg操作都需要
    AVCodecContext * codec_ctx = 0; // ffmpeg编码上下文
    AVCodec * codec = 0;        // ffmpeg编码器
    AVPacket * packet = 0;      // ffmpag单帧数据包
    AVFrame * frame = 0;        // ffmpeg单帧缓存

    FILE * out_file = NULL;     // 用于文件操作
    int audio_stream_index = -1;    // 音频序号

    //注册所有容器解码器
    av_register_all();
    fmt_ctx = avformat_alloc_context();
    if (fmt_ctx == NULL) {
        printf("failed to alloc av format context\n");
        goto END;
    }

    //打开文件
    if (avformat_open_input(&fmt_ctx, infile , NULL, NULL) < 0) {
        printf("open file error");
        goto END;
    }

    //读取音频格式文件信息
    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        printf("find stream info error");
        goto END;
    }

    // 打印出解析到的媒体信息
    av_dump_format(fmt_ctx, 0, infile, 0);

    //获取音频索引
    for (int i = 0; i < fmt_ctx->nb_streams; i++) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_index = i;
            printf("find audio stream index\n");
            break;
        }
    }
    if (audio_stream_index == -1) {
        printf("did not find a audio stream\n");
        goto END;
    }

    //获取解码器
    codec_ctx = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[audio_stream_index]->codecpar);
    codec = avcodec_find_decoder(codec_ctx->codec_id);
    if (codec == NULL) {
        printf("unsupported codec !\n");
        goto END;
    }

    //打开解码器
    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        printf("could not open codec");
        goto END;
    }

    printf("codec name: %s, channels: %d, sample rate: %d, sample format %d\n", codec->name, codec_ctx->channels, codec_ctx->sample_rate, codec_ctx->sample_fmt);

    //分配AVPacket和AVFrame内存,用于接收音频数据,解码数据
    packet = av_packet_alloc();
    frame = av_frame_alloc();
    if (!packet || !frame) {
        printf("failed to alloc packet or frame\n");
        goto END;
    }

    //pcm输出文件
    out_file = fopen(outfile, "wb");
    //将音频数据读入packet
    while (av_read_frame(fmt_ctx, packet) == 0) {
        //取音频索引packet
        if (packet->stream_index == audio_stream_index) {
            int ret = 0;
            // 将封装包发往解码器
            if ((ret = avcodec_send_packet(codec_ctx, packet))) {
                printf("failed to avcodec_send_packet, ret = %d\n", ret);
                break;
            }
            // 从解码器循环拿取数据帧
            while (!avcodec_receive_frame(codec_ctx, frame)) {
                // 获取每个通道每次采样占用几个字节, S16P格式是2字节
                int bytes_num = av_get_bytes_per_sample(codec_ctx->sample_fmt);
                for (int index = 0; index < frame->nb_samples; index++) {
                    // 交错的方式写入
                    for (int channel = 0; channel < codec_ctx->channels; channel++) {
                        fwrite((char *)frame->data[channel] + bytes_num * index, 1, bytes_num, out_file);
                    }
                }
                av_packet_unref(packet);
            }
        }
    }
    printf("decode finish...\n");

    //释放资源
END:
    fclose(out_file);
    if (frame) {
        av_frame_free(&frame);
        printf("free frame.\n");
    }
    if (packet) {
        av_packet_unref(packet);
        printf("free packet.\n");
    }
    if (codec_ctx) {
        avcodec_close(codec_ctx);
        printf("free codec context.\n");
    }
    if (fmt_ctx) {
        avformat_close_input(&fmt_ctx);
        printf("free format context.\n");
    }
}

编码这样写:

int encode(const char * infile, const char *outfile, uint32_t sample_rate, uint8_t channel_num)
{
    // 输入输出文件指针
    FILE * in_file = NULL;
    FILE * out_file = NULL;

    // 打开输入文件
    if ((in_file = fopen(infile, "rb+")) == NULL) {
        printf("failed to open %s \n", infile);
        return -1;
    }

    // 打开输出文件
    if ((out_file = fopen(outfile, "wb+")) == NULL) {
        printf("failed to open %s \n", outfile);
        fclose(in_file);
        return -1;
    }

    // 初始化编码参数
    lame_t lame = lame_init();
    // 设置编码参数
    lame_set_in_samplerate(lame, sample_rate);  
    lame_set_VBR(lame, vbr_default);
    lame_set_num_channels(lame, channel_num);
    // 初始化编码器
    lame_init_params(lame);

    // 编码时用来存放数据的数组,大小建议为 mp3 的采样率 * 1.25 + 7200
    // PCM 通常使用16bit 数据,占用两个字节,如果是双通道,那么读取PCM交错数据时一次最好是 2 * 2 = 4 个字节。
    uint32_t size = (sample_rate * 1.25) / (2 * channel_num) * (2 * channel_num) + 7200;

    // 申请存放数据内存, 如果申请出错,需要释放占用的资源
    int16_t * pcm_buffer = NULL;
    uint8_t * mp3_buffer = NULL;
    pcm_buffer = (int16_t *)malloc(size);
    mp3_buffer = (uint8_t *)malloc(size);
    if (!pcm_buffer || !mp3_buffer) {
        if (pcm_buffer)
            free(pcm_buffer);
        if (mp3_buffer)
            free(mp3_buffer);
        lame_close(lame);
        fclose(in_file);
        fclose(out_file);

        printf("buffer malloc error!\n");
        return -1;
    }

    printf("encode start...\n");

    // 读取 PCM 的字节数目
    size_t read_num = 0;

    do {
        // 读取 PCM 数据
        read_num = fread(pcm_buffer, 1, size, in_file);
        // 转换 MP3 数据,如果获得的数目是0,说明转换结束,需要把 lame 转换剩余的数据全部存放到 MP3 数组里面。
        int write_num = 0;
        if (read_num == 0) {
            write_num = lame_encode_flush(lame, mp3_buffer, size);
        } else {
            write_num = lame_encode_buffer_interleaved(lame, pcm_buffer, static_cast<int>(read_num / sizeof(int16_t) / channel_num), mp3_buffer, size);
        }
        // 转换后的数据写入文件。
        fwrite(mp3_buffer, write_num, 1, out_file);
    } while (read_num > 0);

    printf("encode finish\n");

    // 给文件添加 MP3 的 TAG 信息。
    lame_mp3_tags_fid(lame, out_file);
    // 释放资源
    lame_close(lame);
    fclose(in_file);
    fclose(out_file);

    return 0;
}

参考: https://blog.csdn.net/weixin_41353840/article/details/108000466

8. 解码 pcm 当中容易碰到的坑和相应的编码格式,可以参考:

https://blog.csdn.net/qq21497936/article/details/108799279 https://blog.csdn.net/leixiaohua1020/article/details/50534316

9. 雷神关于编码和解码相关的文章:

https://blog.csdn.net/leixiaohua1020/article/details/50534316 https://blog.csdn.net/leixiaohua1020/article/details/42181571 https://blog.csdn.net/leixiaohua1020/article/details/8652605 https://blog.csdn.net/leixiaohua1020/article/details/15811977 https://blog.csdn.net/leixiaohua1020/article/details/50534316 https://blog.csdn.net/leixiaohua1020/article/list/3

10. ffmpeg 读取码率和帧信息的可以参考:

https://blog.51cto.com/ticktick/1869849 https://blog.51cto.com/ticktick/1872008 https://blog.51cto.com/ticktick/1867059

11. ffmpeg 的例程可以参考:

https://stackoverflow.com/questions/2641460/ffmpeg-c-api-documentation-tutorial

12. av_register_all() 这个函数废弃了。

参考:https://github.com/leandromoreira/ffmpeg-libav-tutorial/issues/29

13. 测试的 mp3 可以从这个网站下载:

http://www.goodkejian.com/erge.htm

14. 命令行形式使用 ffmpeg

参考: https://segmentfault.com/a/1190000016652277 https://cloud.tencent.com/developer/article/1566587

15. 解码例程可以参考:

https://github.com/iamyours/FFmpegAudioPlayer https://gitee.com/zouwm1995/ffmpeg-demo/blob/master/demo/2.%E8%A7%A3%E7%A0%81/decode.c https://www.jianshu.com/p/8ff162ac55bd https://blog.csdn.net/weixin_44721044/article/details/104736782 https://bbs.csdn.net/topics/390401066

16. 编译 ffmpeg

https://www.cnblogs.com/CoderTian/p/6655568.html

17. S16 变成了 S16P,参考:

https://blog.csdn.net/chinabinlang/article/details/47616257 https://blog.csdn.net/disadministrator/article/details/43734335

18. 查看当前的 ffmpeg 支持哪些编码解码器,类似于这样。

ffmpeg -codecs | grep mp3

19. 用于 ffmpeg 的 cmake 可以参考:

https://www.cnblogs.com/liuxia19872003/archive/2012/11/09/2763173.html