导读

在后面的咱们应用FFmpeg进行相干实际,对音视视频进行理解封装、解码等相干操作,明天咱们持续应用FFmpeg进行实际,应用FFmpeg进行音视频编码。

工作一:
在后面《FFmpeg连载4-音频解码》咱们将音频解码成PCM并输入到本地文件,明天咱们就把这个输入到本地的PCM文件进行读取从新编码成AAC音频文件并输入到本地。

工作二:
在《FFmpeg连载3-视频解码》一节中咱们将视频解码成YUV并且输入到本地文件,明天咱们读取这个输入的YUV本地文件进行从新编码成H264视频文件并输入到本地。

H264编码规格简介

因为在设置编码器参数时须要用到profile,所以在这里简略介绍下H264的几种profile规格。

1、Baseline Profile

反对I/P帧,只反对无交织(Progressive)和CAVLC
个别用于低阶或须要额定容错的利用,比方视频通话、手机视频等即时通信畛域

2、Extended Profile

在Baseline的根底上减少了额定的性能,反对流之间的切换,改良误码性能
反对I/P/B/SP/SI帧,只反对无交织(Progressive)和CAVLC
适宜于视频流在网络上的传输场合,比方视频点播

3、Main Profile

提供I/P/B帧,反对无交织(Progressive)和交织(Interlaced),反对CAVLC和CABAC
用于支流消费类电子产品规格如低解码(相对而言)的MP4、便携的视频播放器、PSP和iPod等。

4、High Profile

最罕用的规格
在Main的根底上减少了8x8外部预测、自定义量化、无损视频编码和更多的YUV格局(如4:4:4)
High 4:2:2 Profile(Hi422P)
High 4:4:4 Predictive Profile(Hi444PP)
High 4:2:2 Intra Profile
High 4:4:4 Intra Profile
用于播送及视频碟片存储(蓝光影片),高清电视的利用

YUV视频编码

在后面解码的文章中咱们介绍了一组解码的函数avcodec_send_packetavcodec_receive_frame,同样对于编码也有对应的一组函数,它们是avcodec_send_frameavcodec_receive_packet,
同样一个的调用avcodec_send_frame须要对应多个avcodec_receive_packet的接管。

相干代码及正文如下:

VideoEncoder.hclass VideoEncoder {public:    void encode_yuv_to_h264(const char *yuv_path,const char *h264_path);};

C++实现文件:

#include "VideoEncoder.h"#include <iostream>extern "C"{#include "libavcodec/avcodec.h"#include <libavformat/avformat.h>#include <libavutil/avutil.h>#include <libavutil/opt.h>}static FILE *h264_out = nullptr;void encode_video(AVCodecContext* avCodecContext,AVFrame* avFrame,AVPacket* avPacket){    int ret = avcodec_send_frame(avCodecContext,avFrame);    if(ret < 0){        std::cout << "yuv发送编码失败" << std::endl;    }    while (true){        ret = avcodec_receive_packet(avCodecContext,avPacket);        if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){            std::cout << "须要输送更多yuv数据" << std::endl;            break;        }        std::cout << "写入文件h264" << std::endl;        fwrite(avPacket->data,1,avPacket->size,h264_out);        av_packet_unref(avPacket);    }}void VideoEncoder::encode_yuv_to_h264(const char *yuv_path, const char *h264_path) {    const AVCodec *avCodec = avcodec_find_encoder(AV_CODEC_ID_H264);    AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodec);    avCodecContext->time_base = {1,25};    // 配置编码器参数    avCodecContext->width = 720;    avCodecContext->height = 1280;    avCodecContext->bit_rate = 2000000;    avCodecContext->profile = FF_PROFILE_H264_MAIN;    avCodecContext->gop_size = 10;    avCodecContext->time_base = {1,25};    avCodecContext->framerate = {25,1};    // b帧的数量    avCodecContext->max_b_frames = 1;    avCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;    // 设置H264的编码器参数为提早模式,进步编码品质,然而会造成编码速度降落    av_opt_set(avCodecContext->priv_data,"preset","slow",0);    // 关上编码器    int ret = avcodec_open2(avCodecContext,avCodec, nullptr);    if(ret < 0){        std::cout << "编码器关上失败:" << strerror(ret) << std::endl;        // todo 在析构函数中开释资源        return;    }    AVPacket *avPacket = av_packet_alloc();    AVFrame *avFrame = av_frame_alloc();    avFrame->width = avCodecContext->width;    avFrame->height = avCodecContext->height;    avFrame->format = avCodecContext->pix_fmt;    // 为frame调配buffer    av_frame_get_buffer(avFrame,0);    av_frame_make_writable(avFrame);    h264_out = fopen(h264_path,"wb");    // 读取yuv数据送入编码器    FILE *input_media = fopen(yuv_path,"r");    if(nullptr == input_media){        std::cout << "输出文件关上失败" << std::endl;        return;    }    int pts = 0;    while (!feof(input_media)){        int64_t frame_size = avFrame->width * avFrame->height * 3 / 2;        int64_t read_size = 0;        // 这里能够自行理解下ffmpeg字节对齐的问题        if(avFrame->width == avFrame->linesize[0]){            std::cout << "不存在padding字节" << std::endl;            // 读取y            read_size += fread(avFrame->data[0],1,avFrame->width * avFrame->height,input_media);            // 读取u            read_size += fread(avFrame->data[1],1,avFrame->width * avFrame->height / 4,input_media);            // 读取v            read_size += fread(avFrame->data[2],1,avFrame->width * avFrame->height / 4,input_media);        } else{            std::cout << "存在padding字节" << std::endl;            // 须要对YUV重量进行逐行读取            for (int i = 0; i < avFrame->height;i++) {                // 读取y                read_size += fread(avFrame->data[0] + i * avFrame->linesize[0],1,avFrame->width,input_media);            }            // 读取u和v            for (int i = 0; i < avFrame->height / 2; i++) {                read_size += fread(avFrame->data[1] + i * avFrame->linesize[1],1,avFrame->width / 2,input_media);                read_size += fread(avFrame->data[2] + i * avFrame->linesize[2],1,avFrame->width / 2,input_media);            }        }        pts += (1000000 / 25);        avFrame->pts = pts;        if(read_size != frame_size){            std::cout << "读取数据有误:" << std::endl;        }        encode_video(avCodecContext,avFrame,avPacket);    }    // 冲刷编码器    encode_video(avCodecContext, nullptr,avPacket);    fflush(h264_out);}

须要留神的是在读取YUV数据填充AVFrame时须要辨别开释存在字节对齐的问题。

AAC简略介绍

AAC(Advanced Audio Coding,译为:高级音频编码),是由Fraunhofer IIS、杜比实验室、AT&T、Sony、Nokia等公司共同开发的有损音频编码和文件格式。

AAC相较于MP3的有更多的改良:
1、更多的采样率抉择:8kHz ~ 96kHz,MP3为16kHz ~ 48kHz
2、更高的声道数下限:48个,MP3在MPEG-1模式下为最多双声道,MPEG-2模式下5.1声道
3、改良的压缩性能:以较小的文件大小提供更高的品质
4、......等等等

AAC是一个宏大家族,为了适应不同场合的须要,它有很多种规格可供选择。上面简略介绍几种常见的规格:
1、AAC Main:主规格
2、AAC LC:低复杂度规格(Low Complexity),适宜中等比特率,比方96kbps ~ 192kbps之间。当初的手机比拟常见的MP4文件中的音频局部应用了该规格
3、AAC HE:高效率规格(High Efficiency),适宜低比特率,HE有v1和v2两个版本,其中v1适宜48kbps ~ 64kbps;v2适宜低于32kbps,可在低至32kbps的比特率下提供靠近CD品质的声音。
貌似FFmpeg自带的AAC编码器不反对这个???

PCM音频编码

间接上代码吧...

/** * 将PCM编码成AAC */extern "C"{#include <libavcodec/avcodec.h>#include <libavutil/log.h>#include <libavformat/avformat.h>#include <libavutil/samplefmt.h>#include <libavutil/common.h>#include <libavutil/channel_layout.h>}class AudioEncoder{public:    /**     * 保持编码器开释反对该采样格局     * @param codec     * @param sample_fmt     * @return     */    bool check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)    {        const enum AVSampleFormat *p = codec->sample_fmts;        while (*p != AV_SAMPLE_FMT_NONE) { // 通过AV_SAMPLE_FMT_NONE作为结束符            if (*p == sample_fmt)                return true;            p++;        }        return false;    }    /**     * 查看编码器开释反对该采样率     * @param codec     * @param sample_rate     * @return     */    bool check_sample_rate(const AVCodec *codec, const int sample_rate)    {        const int *p = codec->supported_samplerates;        while (*p != 0)  {// 0作为退出条件,比方libfdk-aacenc.c的aac_sample_rates            printf("%s support %dhz\n", codec->name, *p);            if (*p == sample_rate)                return true;            p++;        }        return false;    }    void encode_pcm_to_aac(const char *pcm_path,const char *aac_path){        av_log_set_level(AV_LOG_DEBUG);        const AVCodec *avCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);        AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodec);        avCodecContext->sample_rate = 44100;        // 默认的aac编码器输出的PCM格局为:AV_SAMPLE_FMT_FLTP        avCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;        avCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;        avCodecContext->bit_rate = 128 * 1024;        avCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;        avCodecContext->channels   = av_get_channel_layout_nb_channels(avCodecContext->channel_layout);        avCodecContext->profile = FF_PROFILE_MPEG2_AAC_HE;        //ffmpeg默认的aac是不带adts,而fdk_aac默认带adts,这里咱们强制不带        avCodecContext->flags = AV_CODEC_FLAG_GLOBAL_HEADER;        /* 检测反对采样格局反对状况 */        if (!check_sample_fmt(avCodec, avCodecContext->sample_fmt)) {            av_log(nullptr, AV_LOG_DEBUG,"Encoder does not support sample format %s",                    av_get_sample_fmt_name(avCodecContext->sample_fmt));            return;        }        if (!check_sample_rate(avCodec, avCodecContext->sample_rate)) {            av_log(nullptr, AV_LOG_DEBUG,"Encoder does not support sample rate %d", avCodecContext->sample_rate);            return;        }        AVFormatContext *avFormatContext = avformat_alloc_context();        const AVOutputFormat *avOutputFormat = av_guess_format(nullptr,aac_path, nullptr);        avFormatContext->oformat = avOutputFormat;        AVStream *aac_stream = avformat_new_stream(avFormatContext,avCodec);        // 关上编码器        int ret = avcodec_open2(avCodecContext,avCodec, nullptr);        if(ret < 0){            char error[1024];            av_log(nullptr, AV_LOG_DEBUG,"编码器关上失败: %s",                   av_strerror(ret,error,1024));        }        // 编码信息拷贝,放在关上编码器之后        ret = avcodec_parameters_from_context(aac_stream->codecpar,avCodecContext);        // 关上输入流        avio_open(&avFormatContext->pb,aac_path,AVIO_FLAG_WRITE);        ret = avformat_write_header(avFormatContext, nullptr);        if(ret < 0){            char error[1024];            av_log(nullptr, AV_LOG_DEBUG,"avformat_write_header fail: %s",                   av_strerror(ret,error,1024));            return;        }        AVPacket *avPacket = av_packet_alloc();        AVFrame *avFrame = av_frame_alloc();        avFrame->channel_layout = avCodecContext->channel_layout;        avFrame->format = avCodecContext->sample_fmt;        avFrame->channels = avCodecContext->channels;        // 每次送多少数据给编码器 aac是1024个采样点        avFrame->nb_samples = avCodecContext->frame_size;        // 调配buffer        av_frame_get_buffer(avFrame,0);        // 每帧数据大小        int per_sample = av_get_bytes_per_sample(static_cast<AVSampleFormat>(avFrame->format));        FILE *pcm_file = fopen(pcm_path,"rb");        int64_t pts = 0;        while (!feof(pcm_file)){            // 设置可写            ret = av_frame_make_writable(avFrame);            // 从输出文件中交替读取各个声道的数据            for (int i = 0; i < avFrame->nb_samples; ++i) {                for (int ch = 0; ch < avCodecContext->channels; ++ch) {                    fread(avFrame->data[ch] + per_sample * i,1,per_sample,pcm_file);                }            }            // 设置pts 应用采样率作为pts的单位,具体换算成秒 pts*1/采样率            pts += avFrame->nb_samples;            avFrame->pts = pts;            if(ret < 0){                char error[1024];                av_strerror(ret,error,1024);                av_log(nullptr, AV_LOG_DEBUG,"av_samples_fill_arrays fail: %s",error);                return;            }            // 送去编码            ret = avcodec_send_frame(avCodecContext,avFrame);            if(ret < 0){                char error[1024];                av_strerror(ret,error,1024);                av_log(nullptr, AV_LOG_DEBUG,"avcodec_send_frame fail: %s",                       error);                return;            }            while (true){                ret = avcodec_receive_packet(avCodecContext,avPacket);                if(ret == AVERROR_EOF || ret == AVERROR(EAGAIN)){                    // 须要更多数据                    av_log(nullptr, AV_LOG_DEBUG,"avcodec_receive_packet need more data");                    break;                } else if(ret < 0){                    char error[1024];                    av_log(nullptr, AV_LOG_DEBUG,"avcodec_receive_packet fail: %s",                           av_strerror(ret,error,1024));                    break;                } else{                    avPacket->stream_index = aac_stream->index;                    av_interleaved_write_frame(avFormatContext,avPacket);                    av_packet_unref(avPacket);                }            }        }        av_write_trailer(avFormatContext);        // 敞开        avio_close(avFormatContext->pb);        av_packet_free(&avPacket);        av_frame_free(&avFrame);    }};

todo与思考

1、在下面的例子中咱们编码进去的视频文件,如果通过ffprobe命令来看出相干媒体信息的话,是没有时长的,也就是说咱们尽管编码胜利了,然而视频的工夫戳失落了,如果想要编码出正确的工夫那该如何解决呢?

编码进去的音频的时长是对的,能够参考下音频是怎么计算的。

2、无论是编码器还是解码器,在没有更多数据输送时都应该发送空的数据包进行数据冲刷,以达到将解码器或编码器外部所有数据获取完的目标...

举荐浏览

FFmpeg连载1-开发环境搭建
FFmpeg连载2-拆散视频和音频
FFmpeg连载3-视频解码
FFmpeg连载4-音频解码

关注我,一起提高,人生不止coding!!!