图像视频编码和FFmpeg(6)-----用FFmpeg编码和解码的例子

        前面大概地介绍了图像格式和不同格式之间的转换、h264的一些知识和FFmpeg编码和解码的基本流程。现在分别为编码和解码给出一个例子。例子是使用参考output_example.cpp和前面介绍中说到的老外写的一个例子。不说那么多了,上代码。

        

        编码:

#ifdef __cplusplus
 #define __STDC_CONSTANT_MACROS
 #ifdef _STDINT_H
  #undef _STDINT_H
 #endif
 # include <stdint.h>
#endif


extern "C"
{
#include<libavcodec/avcodec.h>
#include<libavformat/avformat.h>
#include<libswscale/swscale.h>
}

#include <iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

using namespace std;


int g_width = 352;
int g_height = 288;
int g_video_outbuff_size;
uint8_t* g_video_outbuff = NULL;

AVPixelFormat g_pix_fmt = AV_PIX_FMT_YUV420P;

//init Video Stream and return it
AVStream* getVideoStream(AVFormatContext* fmt_ctx)
{
    AVStream* stream = NULL;

    stream = avformat_new_stream(fmt_ctx, NULL);
    if( stream == NULL)
    {
        fprintf(stderr, "new stream fail\n");
        exit(1);
    }

    AVCodecContext* codec_ctx = stream->codec;

    codec_ctx->codec_id = fmt_ctx->oformat->video_codec;
    codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;

    codec_ctx->bit_rate = 400000;
    codec_ctx->gop_size = 3;

    codec_ctx->pix_fmt = g_pix_fmt;
    codec_ctx->width = g_width;
    codec_ctx->height = g_height;

    codec_ctx->time_base.num = 1;
    codec_ctx->time_base.den = 25;

    codec_ctx->me_range = 16;
    codec_ctx->max_qdiff = 4;
    codec_ctx->qmin = 10;
    codec_ctx->qmax = 51;
    codec_ctx->qcompress = 0.6;

    if( codec_ctx->codec_id == CODEC_ID_MPEG2VIDEO )
        codec_ctx->max_b_frames = 2;

    if( codec_ctx->codec_id == CODEC_ID_MPEG1VIDEO)
        codec_ctx->mb_decision = 2;


    // some formats want stream headers to be separate
    if(!strcmp(fmt_ctx->oformat->name, "mp4")
            || !strcmp(fmt_ctx->oformat->name, "mov")
            || !strcmp(fmt_ctx->oformat->name, "3gp")
       )
    {
        codec_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }

    return stream;
}

void initEncoder(AVStream* stream)
{
    AVCodecContext* codec_ctx = stream->codec;

    AVCodec* encoder = avcodec_find_encoder(codec_ctx->codec_id);
    if( encoder == NULL )
    {
        fprintf(stderr, "cann't find the encoder\n");
        exit(1);
    }

    if( avcodec_open2(codec_ctx, encoder, NULL) < 0 )
    {
        fprintf(stderr, "could not open video codec\n");
        exit(1);
    }
}


AVFrame* getAVFrame()
{
    int size = avpicture_get_size(g_pix_fmt, g_width, g_height);
    uint8_t* buff = (uint8_t*)av_malloc(size);

    if( buff == NULL)
    {
        fprintf(stderr, "av malloc fail\n");
        exit(1);
    }

    AVFrame* frame = av_frame_alloc();
    if( frame == NULL)
    {
        fprintf(stderr, "alloc frame fail\n");
        exit(1);
    }

    avpicture_fill((AVPicture*)frame, buff, g_pix_fmt,
                   g_width, g_height);

    return frame;
}


void writeFrame(AVFormatContext* fmt_ctx, AVStream* stream, AVFrame* frame)
{
    int ret, out_size;
    AVPacket packet;
    AVCodecContext* codec_ctx = stream->codec;

    if (fmt_ctx->oformat->flags & AVFMT_RAWPICTURE) {
        /* raw video case. The API will change slightly in the near
           futur for that */

        av_init_packet(&packet);

        packet.flags |= AV_PKT_FLAG_KEY;
        packet.stream_index= stream->index;
        packet.data= (uint8_t *)frame;
        packet.size= sizeof(AVPicture);

        ret = av_write_frame(fmt_ctx, &packet);
    }
    else {
        /* encode the image */
        out_size = avcodec_encode_video(codec_ctx, g_video_outbuff, g_video_outbuff_size, frame);
        /* if zero size, it means the image was buffered */
        if (out_size > 0) {
            av_init_packet(&packet);


            if(codec_ctx->coded_frame->key_frame)
                packet.flags |= AV_PKT_FLAG_KEY;


            packet.stream_index= stream->index;
            packet.data= g_video_outbuff;
            // not the video_outbuf_size, note!
            packet.size= out_size;

            /* write the compressed frame in the media file */
            ret = av_write_frame(fmt_ctx, &packet);
        }
        else {
            ret = 0;
        }
    }
    if (ret != 0)
    {
        fprintf(stderr, "Error while writing video frame\n");
        exit(1);
    }
}

int main(int argc, char** argv)
{

    const char* input_file = argc < 2 ? "flower_cif.yuv" : argv[1];
    FILE* fin = fopen(input_file, "rb");
    if( fin == NULL )
    {
        fprintf(stderr, "cann't open the yuv file");
        return -1;
    }

    const char* output_filename = argc < 3 ? "flower.mpeg" : argv[2];

    g_width = 352;
    g_height = 288;

    av_register_all();


    //正如其函数名,该函数就是在猜。根据文件名的后缀猜应该使用什么编码
    //当然,也可能找不到对应后缀的编码方法。此时返回NULL。
    AVOutputFormat* output_fmt = av_guess_format(NULL, output_filename, NULL);
    if( output_fmt == NULL )
    {
        fprintf(stderr, "Couldn't deduce output format from file extension: using MPEG.\n");
        output_fmt = av_guess_format("mpeg", NULL, NULL);
    }

    if( output_fmt == NULL )
    {
        fprintf(stderr, "Could not find suitable output format");
        return -1;
    }

    //负责申请一个AVFormatContext结构的内存,并进行简单初始化
    //需要使用avformat_free_context()来释放
    //avformat_free_context()可以用来释放该结构里的所有东西以及该结构本身
    AVFormatContext* fmt_ctx = avformat_alloc_context();
    if( fmt_ctx == NULL )
    {
        fprintf(stderr, "Memory error");
        return -1;
    }

    //把要使用的编码器复制给format_ctx
    fmt_ctx->oformat = output_fmt;
    strncpy(fmt_ctx->filename, output_filename, sizeof(fmt_ctx->filename));



    //http://www.ffmpeg.org/doxygen/1.0/group__lavc__core.html
    //can see #define 	CodecID   AVCodecID
    if( output_fmt->video_codec == AV_CODEC_ID_NONE)
        return -1;

    AVStream* stream = getVideoStream(fmt_ctx);

    av_dump_format(fmt_ctx, 0, output_filename, 1);
    initEncoder(stream);

    if( avio_open(&fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0)
    {
        fprintf(stderr, "cann't open the output file\n");
        return -1;
    }


    if (!(fmt_ctx->oformat->flags & AVFMT_RAWPICTURE)) {

        /* allocate output buffer */
        /* XXX: API change will be done */
        /* buffers passed into lav* can be allocated any way you prefer,
           as long as they're aligned enough for the architecture, and
           they're freed appropriately (such as using av_free for buffers
           allocated with av_malloc) */
        g_video_outbuff_size = 200000;
        g_video_outbuff = (uint8_t *)av_malloc(g_video_outbuff_size);
    }

    AVFrame* frame = getAVFrame();


    if( avformat_write_header(fmt_ctx, NULL) < 0 )
    {
        fprintf(stderr, "cann't write the file head\n");
        return -1;
    }


    int yuv_frame_size = avpicture_get_size(g_pix_fmt, g_width, g_height);
    int ret = 0;

    while( 1 )
    {
        ret = fread(frame->data[0], 1, yuv_frame_size, fin);
        if( ret != yuv_frame_size )
        {
            fprintf(stderr, "%d don't read enough data\n", ret);
            break;
        }

        writeFrame(fmt_ctx, stream, frame);
    }


    av_write_trailer(fmt_ctx);
    avio_close(fmt_ctx->pb);

    av_free(fmt_ctx);

    return 0;
}
        在getVideoStream函数中,可以设置视频的比特率。对其进行修改可以发现:

        mp4格式的文件比mpg有更大的压缩比。当比特率取400000(40Kbps)时,mpg文件就已经比较清晰了,但对应的mp4文件却很模糊。同时可以看到两者的文件大小也差了10多倍。如果把比特率调为40000000,那么得到的mp4文件才比较清晰,当然此时的mp4文件已经增大了10多倍,比刚才400000比特率下的mpg文件还要大。


        解码:这部分基本和老外写的例子是一样的,我加入了一些注释,并保存到bmp图片格式中。

#ifdef __cplusplus
 #define __STDC_CONSTANT_MACROS
 #ifdef _STDINT_H
  #undef _STDINT_H
 #endif
 # include <stdint.h>
#endif

extern "C"
{
#include<libavcodec/avcodec.h>
#include<libavformat/avformat.h>
#include<libavutil/log.h>
#include<libswscale/swscale.h>
}



#include <iostream>
#include<stdio.h>
using namespace std;


#include <windows.h>

bool saveAsBitmap(AVFrame *pFrameRGB, int width, int height, int iFrame)
{
      FILE *pFile = NULL;
      BITMAPFILEHEADER bmpheader;
      BITMAPINFO bmpinfo;

      char fileName[32];
      int bpp = 24;

      // open file
      sprintf(fileName, "frame%d.bmp", iFrame);
      pFile = fopen(fileName, "wb");
      if (!pFile)
            return false;

      bmpheader.bfType = ('M' <<8)|'B';
      bmpheader.bfReserved1 = 0;
      bmpheader.bfReserved2 = 0;
      bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
      bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp/8;

      bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
      bmpinfo.bmiHeader.biWidth = width;
      bmpinfo.bmiHeader.biHeight = -height; //reverse the image
      bmpinfo.bmiHeader.biPlanes = 1;
      bmpinfo.bmiHeader.biBitCount = bpp;
      bmpinfo.bmiHeader.biCompression = BI_RGB;
      bmpinfo.bmiHeader.biSizeImage = 0;
      bmpinfo.bmiHeader.biXPelsPerMeter = 100;
      bmpinfo.bmiHeader.biYPelsPerMeter = 100;
      bmpinfo.bmiHeader.biClrUsed = 0;
      bmpinfo.bmiHeader.biClrImportant = 0;

      fwrite(&bmpheader, sizeof(BITMAPFILEHEADER), 1, pFile);
      fwrite(&bmpinfo.bmiHeader, sizeof(BITMAPINFOHEADER), 1, pFile);
      uint8_t *buffer = pFrameRGB->data[0];
      for (int h=0; h<height; h++)
      {
            for (int w=0; w<width; w++)
            {
                  fwrite(buffer+2, 1, 1, pFile);
                  fwrite(buffer+1, 1, 1, pFile);
                  fwrite(buffer, 1, 1, pFile);

                  buffer += 3;
            }
      }
      fclose(pFile);

      return true;
}



int main(int argc, char** argv)
{
    //This registers all available file formats and codecs with
    //the library so they will be used automatically
    //when a file with the corresponding format/codec is opened.
    //Note that you only need to call av_register_all() once,
    av_register_all();

    char err_msg[50];
    int ret;
    const char* filename = argc > 1 ? argv[1] : "19.mp4";

    //在使用前,一定要初始化,有网友就是因为没有初始化而被坑
    //avformat_open_input其内部实现为:
    //int avformat_open_input(AVFormatContext **ps, const char *filename,
//    AVInputFormat *fmt, AVDictionary **options)
//    {
//          AVFormatContext *s = *ps;
//          int ret = 0;
//          AVDictionary *tmp = NULL;
//          ID3v2ExtraMeta *id3v2_extra_meta = NULL;

//          if (!s && !(s = avformat_alloc_context()))
//          return AVERROR(ENOMEM);


//          ....
//     }
    AVFormatContext* format_ctx = NULL;

    //我用的是2.2.1版本,没有av_open_input_file函数了,可以到
    //http://ffmpeg.zeranoe.com/builds/win32/dev/
    //下载低版本的ffmpeg。
    //第三个参数用来指定视频的格式,第四个参数是格式选项。设置为NULL即可。
    //libavformat可以自动获取。
    if( (ret = avformat_open_input(&format_ctx, filename, NULL, NULL)) < 0 )
    {
        //如果是gcc编译的话,用av_err2str是没有问题的,但用C++编译就有问题。
        //查看av_err2str是一个宏。其实现用了一个临近数组吧。
        //fun( (char[34]){0} );像这种形式,c语言支持这种形式,
        //但C++不支持。所以,不能用av_err2str。取而代之用av_strerror
        av_strerror(ret, err_msg, sizeof(err_msg) );
        fprintf(stderr, "Cann't open the file: %s", err_msg);
        return -1;
    }

    //通过读取媒体文件的中的包来获取媒体文件中的流信息
    //也就是把媒体文件中的音视频流等信息读出来,保存在容器中,以便解码时使用
    if( (ret = avformat_find_stream_info(format_ctx, NULL)) < 0 )
    {
        av_strerror(ret, err_msg, sizeof(err_msg) );
        fprintf(stderr, "cann't find stream info: %s", err_msg);
        return -1;
    }

    //这个函数将填充format_ctx->streams成员。
    //注意,并不是用filename来填充.
    //streams的声明是 AVStream * streams[MAX_STREAMS]
    //最后一个参数输出是否为output
    av_dump_format(format_ctx, 0, filename, 0);


    //此时,streams成员数组指向了一系列的帧,帧数由nb_streams成员指明
    int video_stream = -1;
    int i;
    for(i = 0; i < format_ctx->nb_streams; ++i)
    {
        //查找第一个视频帧
        if( format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
        {
            video_stream = i;
            break;
        }
    }

    if( video_stream == -1 )
    {
        fputs("cann't find the video frame", stderr);
        return -1;
    }

    //stream里面关于编解码器的信息 被称为 编解码器上下文。
    //其包含了stream会使用的所有编解码器信息。现在用codec_ctx指向其
    AVCodecContext* codec_ctx = format_ctx->streams[video_stream]->codec;


    //下面来获取编解码器,并打开之
    //查找解码器之前,必须先调用av_register_all注册所有支持的解码器
    //查找成功返回解码器指针,否则返回NULL
    //音视频解码器保存在一个链表中,查找过程中,函数从头到尾遍历链表,通过比较解码器的ID来查找
    AVCodec* decoder = avcodec_find_decoder(codec_ctx->codec_id);
    if( decoder == NULL )
    {
        fputs("Unsupported codec!\n", stderr);
        return -1;
    }

    //AVDictionary* option_dict = NULL;
    //使用给定的AVCodec初始化AVCodecContext
    if( avcodec_open2(codec_ctx, decoder, NULL) < 0)
    {
        fputs("Could not open codec\n", stderr);
        return -1;
    }

    //获取存放一个帧的空间
    AVFrame* frame = av_frame_alloc();
    //av_molloc只是malloc函数的简单包装,
    //其保证分配的地址都是经过对齐的。当我们不再使用的时候,
    //必须要用av_free来释放内存
    AVFrame* frame_rgb = av_frame_alloc();

    if( frame == NULL || frame_rgb == NULL)
    {
        fputs("alloc frame place fail", stderr);
        return -1;
    }


    //虽然有了申请存放帧的空间,但还需要申请一个空间来存放帧对应的原始数据,
    //因为我们转换的时候需要用到。
    //get the size we need
    int byte_num = avpicture_get_size(PIX_FMT_RGB24, codec_ctx->width,
                                      codec_ctx->height);

    uint8_t* buffer = (uint8_t*)av_malloc(byte_num * sizeof(uint8_t));

    //用avpicture_fill函数把一个帧和一个buffer, 格式、大小关联起来
    //
    avpicture_fill((AVPicture*)frame_rgb, buffer, PIX_FMT_RGB24,
                   codec_ctx->width, codec_ctx->height);

    SwsContext *sws_ctx = sws_getContext(codec_ctx->width,
                                         codec_ctx->height,
                                         codec_ctx->pix_fmt,
                                         codec_ctx->width,
                                         codec_ctx->height,
                                         PIX_FMT_RGB24,
                                         SWS_BILINEAR,
                                         NULL,
                                         NULL,
                                         NULL);


    int yuv420p_bytes = avpicture_get_size(AV_PIX_FMT_YUV420P,
                                           codec_ctx->width,
                                           codec_ctx->height);
    uint8_t* yuv420p_buff = (uint8_t*)av_malloc(yuv420p_bytes);
    AVFrame* yuv420p_frame = av_frame_alloc();
    avpicture_fill((AVPicture*)yuv420p_frame, yuv420p_buff,
                   AV_PIX_FMT_YUV420P, codec_ctx->width,
                   codec_ctx->height);

    SwsContext* yuv_sws_ctx = sws_getContext(codec_ctx->width,
                                             codec_ctx->height,
                                             codec_ctx->pix_fmt,
                                             codec_ctx->width,
                                             codec_ctx->height,
                                             AV_PIX_FMT_YUV420P,
                                             SWS_BILINEAR,
                                             NULL, NULL, NULL);



    //之前的结构体都是声明为指针,内存由ffmpeg分配,而这个是变量。
    //估计是因为这个包的空间肯定会重复使用,没必要要求ffmpeg库分配。
    AVPacket packet;
    int frame_finished;

    i = 0;
    //从视频中读取一个包,并保存到packet中
    while( av_read_frame(format_ctx, &packet) >= 0 )
    {
        //前面我们已经获取了第一个视频帧的下标,video_stream保存了该下标
        if( packet.stream_index == video_stream )
        {
            //对视频帧进行解码.
            //如果有帧被解压,frame_finished将被赋予非0值,否则为0
            //数据将从packet中解压到frame中。
            //在前面的avcodec_open2函数中,codec_ctx获取了解码器。现在将用这个解码器来解码
            avcodec_decode_video2(codec_ctx, frame, &frame_finished,
                                  &packet);


            if( frame_finished )
            {
                //Convert the image from its native format to RGB
                sws_scale(sws_ctx, frame->data, frame->linesize,
                          0, codec_ctx->height,
                          frame_rgb->data, frame_rgb->linesize);


                sws_scale(yuv_sws_ctx, frame->data, frame->linesize,
                          0, codec_ctx->height,
                          yuv420p_frame->data, yuv420p_frame->linesize);

                fwrite(yuv420p_frame->data[0], 1, yuv420p_bytes, fout);

                if( ++i <= 5 )
                {
                    saveAsBitmap(frame_rgb, codec_ctx->width,
                                 codec_ctx->height, i);
                }
            }
        }

        //需要注意的是:我们只是定义了一个packet 结构体。
        //而结构体有一个data指针,我们定义这个变量时,
        //并没有申请一个内存并让data指向之。这个工作有ffmpeg去完成。
        //所以需要调用av_free_packet
        av_free_packet(&packet);
    }

    av_free(frame);
    av_free(frame_rgb);

    av_free(buffer);

    avcodec_close(codec_ctx);
    avformat_close_input(&format_ctx);

    sws_freeContext(sws_ctx);

    return 0;
}

你可能感兴趣的:(ffmpeg)