Ffmpeg视频开发教程(五)————2018最新版ffmpeg开发包(4.0)实现pcm数据编码为mpeg audio音频文件(mp2)

Ffmpeg视频开发教程(五)————2018最新版ffmpeg开发包(4.0)实现pcm数据编码为mpeg audio音频文件(mp2)


网上有很多关于ffmpeg编码音频的文章,但多是基于老版本。而且很多缺胳膊少腿,注释也不够详细。本文基于最新的2018年的ffmpeg 4.0开发包实现音频pcm数据的编码,注释非常详细。比如下面的注释:
//文件的采样率是44100, 格式是AV_SAMPLE_FMT_S16, 有两个通道,即每个通道是16位有符号数据,存储方式为C1C2C1C2.......。其中C1表示第一个通道的16位数,C2表示第二个通道的16位数
//注意后面的编码器的采样个是必须和pcm的格式一致

给出了实现程序一些非常重要的细节。


本文之所以编码为mp2,是因为mp2编码比较简单,能够把一个问题讲清楚。如果选择mp3,那么因为mp3编码器不直接支持AV_SAMPLE_FMT_S16格式的pcm数据,涉及到重采样等其它知识,略微复杂。

本文使用的测试用的pcm文件的下载地址为: https://download.csdn.net/download/zhangamxqun/10440053
压缩效果非常明显,压缩前的pcm文件40多兆,编码后只有2兆多。

本文程序的环境搭建参考我的第一篇FFMPEG教程:https://blog.csdn.net/zhangamxqun/article/details/80304494

下面就是核心代码和最详细的注释:

/**
实现2018版本FFMPEG将pcm音频数据编码为Mepeg Audio文件(mp2),作者自己测试正确可用
作者:明天继续
使用的ffmpeg版本:ffmpeg-20180508-293a6e8-win32
开发工具:vs2012
**/

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern "C" {
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavformat/avformat.h>
#include <libavutil/samplefmt.h>
}

#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avcodec.lib")
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avformat.lib")
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avfilter.lib")
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avutil.lib")

#if _MSC_VER
#define snprintf _snprintf_s
#define PRIx64       "I64x"
#define PRIX64       "I64X"
#endif


/* 检查编码器是否支持指定的采样格式 */
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{
    const enum AVSampleFormat *p = codec->sample_fmts;

    while (*p != AV_SAMPLE_FMT_NONE) {
        if (*p == sample_fmt)
            return 1;
        p++;
    }
    return 0;
}
/* 选择最高的采样率 */
static int select_sample_rate(const AVCodec *codec)
{
    const int *p;
    int best_samplerate = 0;

    if (!codec->supported_samplerates)
        return 44100;

    p = codec->supported_samplerates;
    while (*p) {
        if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
            best_samplerate = *p;
        p++;
    }
    return best_samplerate;
}

/* 最大通道数的层 */
static int select_channel_layout(const AVCodec *codec)
{
    const uint64_t *p;
    uint64_t best_ch_layout = 0;
    int best_nb_channels   = 0;

    if (!codec->channel_layouts)
        return AV_CH_LAYOUT_STEREO;

    p = codec->channel_layouts;
    while (*p) {
        int nb_channels = av_get_channel_layout_nb_channels(*p);

        if (nb_channels > best_nb_channels) {
            best_ch_layout    = *p;
            best_nb_channels = nb_channels;
        }
        p++;
    }
    return best_ch_layout;
}

static void encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt,
                   FILE *output)
{
    int ret;

    /* 编码frame */
    ret = avcodec_send_frame(ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending the frame to the encoder\n");
        exit(1);
    }

    /* 读取编码出的数据包,通常不止一个 */
    while (ret >= 0) {
        ret = avcodec_receive_packet(ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error encoding audio frame\n");
            exit(1);
        }

        fwrite(pkt->data, 1, pkt->size, output);
        av_packet_unref(pkt);
    }
}


int _tmain(int argc, _TCHAR* argvec[])
{
	//输入原始的pcm数据文件
	//文件的采样率是44100, 格式是AV_SAMPLE_FMT_S16, 有两个通道,即每个通道是16位有符号数据,存储方式为C1C2C1C2.......。其中C1表示第一个通道的16位数,C2表示第二个通道的16位数
	//注意后面的编码器的采样个是必须和pcm的格式一致
	char *intput_file = "test.pcm";

	//输出编码后的音频文件,为MPEG Audio 文件
	char *output_file = "out.mp2";	

	av_register_all();
	//编码器
    const AVCodec *codec;
	//编码器上下文
    AVCodecContext *c= NULL;
	//pcm数据帧
    AVFrame *frame;
	//编码后的mp2数据包
    AVPacket *pkt;
    int ret;
	//输出文件
    FILE *f;


    /* 找到MP2的编码器 */
    codec = avcodec_find_encoder(AV_CODEC_ID_MP2);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

	//生成编码器上下文
    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate audio codec context\n");
        exit(1);
    }

    /* 设置编码器上下文的bit率 */
    c->bit_rate = 64000;
	
    /* 检查编码器支持我们输入的pcm数据格式,如果不支持需要进行重采样,后续讨论如何重采样 */
	c->sample_fmt = AV_SAMPLE_FMT_S16;
    if (!check_sample_fmt(codec, c->sample_fmt)) {
        fprintf(stderr, "Encoder does not support sample format %s",
                av_get_sample_fmt_name(c->sample_fmt));
        exit(1);
    }

    /* 设置编码器的必要参数 */
    c->sample_rate    = select_sample_rate(codec);
    c->channel_layout = select_channel_layout(codec);
    c->channels       = av_get_channel_layout_nb_channels(c->channel_layout);

    /* 打开编码器,打开后,会完善一些参数,比如编码器上下文的frame_size */
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

	//打开输出文件
    f = fopen(output_file, "wb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", output_file);
        exit(1);
    }

    /* 初始化mp2编码结果数据的存储包 */
    pkt = av_packet_alloc();
    if (!pkt) {
        fprintf(stderr, "could not allocate the packet\n");
        exit(1);
    }

    /* 初始化存放pcm数据的frame */
    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate audio frame\n");
        exit(1);
    }

	//设置pcm数据frame的一些参数
	//frame size 是数据中有多少个样本,每个样本包含两个通道各一个数据,每个数据占16位2个字节。所以frame得实际大小是 frame_size * sizeof(uint16_t)*2
    frame->nb_samples     = c->frame_size;
    frame->format         = c->sample_fmt;
    frame->channel_layout = c->channel_layout;

    /* 分配frame的内存空间 */
    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate audio data buffers\n");
        exit(1);
    }

	FILE * inputfile = fopen(intput_file, "rb");
	unsigned int readsize = 0;
	 while (!feof(inputfile)) {
		  ret = av_frame_make_writable(frame);
		  if (ret < 0)
            exit(1);
        /* 从pcm文件读取数据 */
        readsize = fread(frame->data[0], sizeof(uint16_t)*2, c->frame_size, inputfile);
        if (!readsize)
            break;
		c->frame_size = readsize;
		encode(c, frame, pkt, f);
	 }
	 fclose(inputfile);

    /* 处理最后的残余数据*/
    encode(c, NULL, pkt, f);

    fclose(f);

    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&c);

    return 0;
}


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值