zoukankan      html  css  js  c++  java
  • ffmpeg编码

    1. 注册所有容器格式和CODEC:av_register_all()
    2. 打开文件:av_open_input_file()
    3. 从文件中提取流信息:av_find_stream_info()
    4. 穷举所有的流,查找其中种类为CODEC_TYPE_VIDEO
    5. 查找对应的解码器:avcodec_find_decoder()
    6. 打开编解码器:avcodec_open()
    7. 为解码帧分配内存:avcodec_alloc_frame()
    8. 不停地从码流中提取出帧数据:av_read_frame()
    9. 判断帧的类型,对于视频帧调用:avcodec_decode_video()
    10. 解码完后,释放解码器:avcodec_close()
    11. 关闭输入文件:av_close_input_file()

    FfmpegEncoder.h

    /*
     * FfmpegEncoder.h
     *
     * Current, Can Support YUV422sp encoder and decoder
     *
     *  Created on: Dec 5, 2010
     *      Author: Henry.Wen
    */
    #ifndef _H264ENCODER_H
    #define _H264ENCODER_H
    
    void save_image(const char* filePath, const void* bufferBase, int width, int height);
    
    int encoder_init(const char* filePath, int width, int height);
    
    int encoder_frame(const void* frame);
    
    int encoder_frame_yuv422(const void* frame);
    
    void encoder_close();
    
    #endif
    

     FfmpegEncoder.cpp

    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    extern "C"
    {
    #include <libavutil/mathematics.h>
    #include <libavformat/avformat.h>
    //#include <libswscale/swscale.h>
    }
    
    #include <skia/core/SkBitmap.h>
    #include <skia/images/SkImageEncoder.h>
    
    #include <android_runtime/AndroidRuntime.h>
    #include "FfmpegEncoder.h"
    
    AVOutputFormat   *g_fmt           = 0;
    AVFormatContext  *g_oc            = 0;
    AVCodec          *g_video_codec   = 0;
    AVStream         *g_video_st      = 0;
    AVFrame          *g_frame         = 0;
    AVPicture         g_picture;
    
    int               g_frame_count   = 0;
    double            g_video_pts     = 0;
    int               g_flagInit      = 0;
    int               g_width         = 0;
    int               g_height        = 0;
    
    using namespace android;
    static Mutex sg_mutexLock;
    
    #ifndef LOGI
    #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO,  "H264ENCODE", __VA_ARGS__))
    #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "H264ENCODE", __VA_ARGS__))
    #endif
    
    void save_image(const char* filePath, const void* bufferBase, int width, int height)
    {
    	Mutex::Autolock lock(sg_mutexLock);
    	SkBitmap b;
    #if (ANDROID_r4_4_0)
    	b.setConfig(SkBitmap::kARGB_8888_Config, width, height,(size_t)0);
    #else
    	b.setConfig(SkBitmap::kARGB_8888_Config, width, height);
    #endif
    
    	b.setPixels((void*)bufferBase);
    	SkImageEncoder::EncodeFile(filePath, b, SkImageEncoder::kJPEG_Type, SkImageEncoder::kDefaultQuality);
    
    	LOGI("save_image image ok====================");
    }
    
    AVStream *add_stream(AVFormatContext *oc, AVCodec **codec, enum AVCodecID codec_id, int width, int height)
    {
        AVCodecContext *c;
        AVStream *st;
    
        /* find the encoder */
        *codec = avcodec_find_encoder(codec_id);
        LOGI("encoder_init add_stream find encoder='%s'", avcodec_get_name(codec_id));
        if (!(*codec))
        {
        	LOGE("encoder_init add_stream could not find encoder for '%s'
    ", avcodec_get_name(codec_id));
        	return 0;
        }
    
        st = avformat_new_stream(oc, *codec);
        if (!st)
        {
        	LOGE("encoder_init add_stream could not allocate stream");
        	return 0;
        }
        st->id = oc->nb_streams-1;
        c = st->codec;
    
        avcodec_get_context_defaults3(c, *codec);
    	c->codec_id      = codec_id;//AV_CODEC_ID_MPEG4;
    
    	c->bit_rate      = 40000;
    	/* Resolution must be a multiple of two. */
    	c->width         = width;
    	c->height        = height;
    	c->time_base.den = 25;
    	c->time_base.num = 1;
    	c->gop_size      = 12; /* emit one intra frame every twelve frames at most */
    	c->pix_fmt       = AV_PIX_FMT_YUV420P;
    
    	if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO)
    	{
    		c->max_b_frames = 2;
    	}
    
    	if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO)
    	{
    		c->mb_decision = 2;
    	}
    
        /* Some formats want stream headers to be separate. */
        if (oc->oformat->flags & AVFMT_GLOBALHEADER)
            c->flags |= CODEC_FLAG_GLOBAL_HEADER;
    
        if(!strcmp(oc->oformat->name, "mp4") || !strcmp(oc->oformat->name, "mov") || !strcmp(oc->oformat->name, "3gp"))
        	c->flags |= CODEC_FLAG_GLOBAL_HEADER;
    
        return st;
    }
    
    AVFrame* alloc_picture(AVPixelFormat pix_fmt, int width, int height)
    {
    	AVFrame *picture;
    	uint8_t *picture_buf;
    	int size;
    
    	picture = avcodec_alloc_frame();
    	if (!picture)
    		return NULL;
    
    	size = avpicture_get_size(pix_fmt, width, height);
    	picture_buf = (uint8_t *)av_malloc(size);
    	if (!picture_buf)
    	{
    		av_free(picture);
    		return NULL;
    	}
    	avpicture_fill((AVPicture *)picture, picture_buf, pix_fmt, width, height);
    	return picture;
    }
    
    int open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st)
    {
        int ret = 0;
        AVCodecContext *c = st->codec;
    
        /* open the codec */
        ret = avcodec_open2(c, codec, NULL);
        if (ret < 0)
        {
        	LOGE("encoder_init open_video could not open video codec: %s", av_err2str(ret));
        	return -1;
        }
    
        g_frame = avcodec_alloc_frame();
    	if (!g_frame)
    	{
    		LOGE("encoder_init open_video could not allocate video frame");
    		return -1;
    	}
    
    	ret = avpicture_alloc(&g_picture, c->pix_fmt, c->width, c->height);
    	if (ret < 0)
    	{
    		LOGE("encoder_init open_video could not allocate picture: %s", av_err2str(ret));
    		free(g_frame);
    		g_frame = 0;
    
    		return -1;
    	}
    	*((AVPicture *)g_frame) = g_picture;
    
        return 0;
    }
    
    void close_video(AVStream *st)
    {
    	if(st->codec)
    	{
    		avcodec_close(st->codec);
    		st->codec = 0;
    	}
    
    	if(g_frame)
    	{
    		av_free(g_picture.data[0]);
    		av_free(g_frame);
    		g_frame = 0;
    	}
    }
    
    int encoder_init(const char* filePath, int width, int height)
    {
    	if(g_flagInit)
    		return 0;
    
    	Mutex::Autolock lock(sg_mutexLock);
    
    	int ret = 0;
    	LOGI("encoder_init ============begin");
    
    	if(!filePath || width <= 0 || height <= 0)
    	{
    		LOGE("encoder_init input parameters error ret = %d", (ret = -1));
    		return -1;
    	}
    	av_register_all();
    
    	/* allocate the output media context */
    	avformat_alloc_output_context2(&g_oc, NULL, NULL, filePath);
    
    	if (!g_oc)
    	{
    		LOGI("Could not deduce output format from file extension: using MPEG.");
    		return -1;
    	}
    
    	g_fmt = g_oc->oformat;
    
    	g_video_st = add_stream(g_oc, &g_video_codec, g_fmt->video_codec, width, height);
    
    	av_dump_format(g_oc, 0, filePath, 1);
    
    	if (g_video_st)
    	{
    		if(open_video(g_oc, g_video_codec, g_video_st) < 0)
    		{
    			LOGE("encoder_init open_video fail!");
    			close_video(g_video_st);
    			av_free(g_oc);
    			g_oc = 0;
    
    			return -1;
    		}
    	}
    	else
    	{
    		LOGE("encoder_init g_video_st is null, not enough memory!");
    		av_free(g_oc);
    		g_oc = 0;
    
    		return -1;
    	}
    
    	/* open the output file, if needed */
    	if (!(g_fmt->flags & AVFMT_NOFILE))
    	{
    		LOGI("encoder_init avio_open ============begin");
    		ret = avio_open(&g_oc->pb, filePath, AVIO_FLAG_WRITE);
    		LOGI("encoder_init avio_open ret:%d============end", ret);
    		if (ret < 0)
    		{
    			LOGE("encoder_init could not open '%s': %s", filePath, av_err2str(ret));
    			close_video(g_video_st);
    			av_free(g_oc);
    			g_oc = 0;
    			return -1;
    		}
    	}
    	LOGI("encoder_init avformat_write_header video file");
    	ret = avformat_write_header(g_oc, NULL);
    
    	if (ret < 0)
    	{
    		LOGE("encoder_init error occurred when opening output file: %s
    ", av_err2str(ret));
    		close_video(g_video_st);
    		av_free(g_oc);
    		g_oc = 0;
    
    		return -1;
    	}
    
    	if (g_frame)
    		g_frame->pts = 0;
    
    	g_flagInit  = 1;
    	g_width     = width;
    	g_height    = height;
    
    	LOGI("encoder_init ============end");
    	return 0;
    }
    static struct SwsContext *swsContext;
    void fill_yuv_image(AVFrame *pict, const void* frame)
    {
        int x, y, tmpIndex = 0, tmpWdith = g_width >> 1, tmpHeight = g_height >> 1;
        unsigned char* tmpBuffer_yuv = (unsigned char*)frame;
        /* Y */
        for (y = 0; y < g_height; ++y)
        {
            for (x = 0; x < g_width; ++x)
            {
                pict->data[0][y * pict->linesize[0] + x] = *(tmpBuffer_yuv + tmpIndex);
                ++tmpIndex;
            }
        }
    
        tmpIndex = 0;
        int tmpLength = g_width * g_height;
        unsigned char* tmpBuffer_uv = tmpBuffer_yuv + tmpLength;
        /* Cb and Cr */
        for (y = 0; y < tmpHeight; ++y)
        {
            for (x = 0; x < tmpWdith; ++x)
            {
                pict->data[1][y * pict->linesize[1] + x] = *(tmpBuffer_uv + tmpIndex + 1);
                pict->data[2][y * pict->linesize[2] + x] = *(tmpBuffer_uv + tmpIndex);
                tmpIndex+= 2;
            }
        }
    
    }
    
    //fill AVFrame with YUV422p buffer
    void fill_yuv422p_image(AVFrame *pict, const void* frameYUV422p)
    {
    	int width = g_width, height = g_height;
    	unsigned char * pyuv422 	= (unsigned char *)frameYUV422p;
    	unsigned char * pyuv420y 	= &pict->data[0][0];
    	unsigned char * pyuv420u	= &pict->data[1][0];
    	unsigned char * pyuv420v	= &pict->data[2][0];
    	int uv_count = 0;
    	int i, j;
    	for (i = 0; i < height; i += 2)
    		for (j = 0; j < width; j += 2) {
    
    			memcpy(pyuv420y + i * width + j, pyuv422 + i * width * 2 + j * 2,
    					1);
    			memcpy(pyuv420y + (i + 1) * width + j,
    					pyuv422 + (i + 1) * width * 2 + j * 2, 1);
    			memcpy(pyuv420y + i * width + (j + 1),
    					pyuv422 + i * width * 2 + (j + 1) * 2, 1);
    			memcpy(pyuv420y + (i + 1) * width + (j + 1),
    					pyuv422 + (i + 1) * width * 2 + (j + 1) * 2, 1);
    			//±£ÁôU ·ÖÁ¿
    			memcpy(pyuv420u + uv_count, pyuv422 + i * width * 2 + j * 2 + 1, 1);
    			//±£ÁôV·ÖÁ¿;
    			memcpy(pyuv420v + uv_count,
    					pyuv422 + (i + 1) * width * 2 + (j + 1) * 2 + 1, 1);
    			uv_count++;
    
    		}
    
    	/*int x, y, tmpIndex = 0, tmpWdith = g_width >> 1, tmpHeight = g_height >> 1;
    	unsigned char* tmpBuffer_yuv = (unsigned char*)frameYUV422p;
    
    	/* Y */
    	/*for (y = 0; y < g_height; ++y)
    	{
    		for (x = 0; x < g_width; ++x)
    		{
    			pict->data[0][y * pict->linesize[0] + x] = *(tmpBuffer_yuv + tmpIndex);
    			++tmpIndex;
    		}
    	}
    
    	tmpIndex = 0;
    	int tmpLength = g_width * g_height;
    	unsigned char* tmpBuffer_uv = tmpBuffer_yuv + tmpLength;
    
    	// Cb and Cr
    	for (y = 0; y < tmpHeight; ++y)
    	{
    		for (x = 0; x < tmpWdith; ++x)
    		{
    			pict->data[1][y * pict->linesize[1] + x] = *(tmpBuffer_uv + tmpIndex);
    			pict->data[2][y * pict->linesize[2] + x] = *(tmpBuffer_uv + tmpIndex +1);
    			tmpIndex += 2;
    		}
    		tmpIndex += g_width;
    	}*/
    }
    
    void write_video_frame(AVFormatContext *oc, AVStream *st)
    {
        int ret;
        static struct SwsContext *sws_ctx;
        AVCodecContext *c = st->codec;
    
        /* encode the image */
    	AVPacket pkt;
    	int got_output;
    
    	av_init_packet(&pkt);
    	pkt.data = NULL;    // packet data will be allocated by the encoder
    	pkt.size = 0;
    
    	ret = avcodec_encode_video2(c, &pkt, g_frame, &got_output);
    	if (ret < 0)
    	{
    		LOGE("encoder_init error encoding video frame: %s
    ", av_err2str(ret));
    		return;
    	}
    
    	//If size is zero, it means the image was buffered.
    	if (got_output)
    	{
    	   if (c->coded_frame->key_frame)
    		   pkt.flags |= AV_PKT_FLAG_KEY;
    
    	   pkt.stream_index = st->index;
    	   ret = av_interleaved_write_frame(oc, &pkt);
    	}
    	else
    	{
    	   ret = 0;
    	}
    	av_free_packet(&pkt);
    
        if (ret != 0)
        {
            LOGE("encoder_init error while writing video frame: %s
    ", av_err2str(ret));
            return;
        }
        ++g_frame_count = 0;
    }
    
    int encoder_frame(const void* frame)
    {
    	if(!g_flagInit)
    		return 0;
    
    	Mutex::Autolock lock(sg_mutexLock);
    
    	fill_yuv_image(g_frame, frame);
    
    	if (g_video_st)
    		g_video_pts = (double)g_video_st->pts.val * g_video_st->time_base.num / g_video_st->time_base.den;
    	else
    		g_video_pts = 0.0;
    
    	write_video_frame(g_oc, g_video_st);
    	g_frame->pts += av_rescale_q(1, g_video_st->codec->time_base, g_video_st->time_base);
    
    	return 0;
    }
    
    
    int encoder_frame_yuv422(const void* frame)
    {
    	if(!g_flagInit)
    		return 0;
    
    	Mutex::Autolock lock(sg_mutexLock);
    
    	fill_yuv422p_image(g_frame, frame);
    
    	if (g_video_st)
    		g_video_pts = (double)g_video_st->pts.val * g_video_st->time_base.num / g_video_st->time_base.den;
    	else
    		g_video_pts = 0.0;
    
    	write_video_frame(g_oc, g_video_st);
    	g_frame->pts += av_rescale_q(1, g_video_st->codec->time_base, g_video_st->time_base);
    
    	return 0;
    }
    
    void encoder_close()
    {
    	LOGI("encoder_close ============begin");
    	Mutex::Autolock lock(sg_mutexLock);
    	if(g_oc)
    	{
    		av_write_trailer(g_oc);
    
    		if (g_video_st)
    			close_video(g_video_st);
    
    		for(int i = 0; i < (int)g_oc->nb_streams; ++i)
    		{
    			av_freep(&g_oc->streams[i]->codec);
    			av_freep(&g_oc->streams[i]);
    		}
    
    		if (!(g_fmt->flags & AVFMT_NOFILE))
    			avio_close(g_oc->pb);
    
    		av_free(g_oc);
    	}
    
    	g_oc            = 0;
    	g_video_st      = 0;
    	g_flagInit      = 0;
    	g_frame_count   = 0;
    	g_width         = 0;
    	g_height        = 0;
    	LOGI("encoder_close ============end");
    }
    

      首先第一件事情就是开一个视频文件并从中得到流。我们要做的第一件事情就是使用av_register_all();来初始化libavformat/libavcodec: 

    这一步注册库中含有的所有可用的文件格式和编码器,这样当打开一个文件时,它们才能够自动选择相应的文件格式和编码器。av_register_all()只需
    调用一次,所以,要放在初始化代码中。也可以仅仅注册个人的文件格式和编码。
      下一步,打开文件:
    AVFormatContext *pFormatCtx;
    const char *filename="myvideo.mpg";
    av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL); // 打开视频文件
    最后三个参数描述了文件格式,缓冲区大小(size)和格式参数;我们通过简单地指明NULL或0告诉 libavformat 去自动探测文件格式并且使用默认的缓
    冲区大小。这里的格式参数指的是视频输出参数,比如宽高的坐标。
      下一步,我们需要取出包含在文件中的流信息:
    av_find_stream_info(pFormatCtx); // 取出流信息
    AVFormatContext 结构体
    dump_format(pFormatCtx, 0, filename, false);//我们可以使用这个函数把获取到得参数全部输出。
    for(i=0; i<pFormatCtx->nb_streams; i++) //区分视频流和音频流
    if(pFormatCtx->streams->codec.codec_type==CODEC_TYPE_VIDEO) //找到视频流,这里也可以换成音频
    {
    videoStream=i;
    break;
    }
    接下来就需要寻找解码器
    AVCodec *pCodec;
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
    avcodec_open(pCodecCtx, pCodec); // 打开解码器
    给视频帧分配空间以便存储解码后的图片:
    AVFrame *pFrame;
    pFrame=avcodec_alloc_frame();
    /////////////////////////////////////////开始解码///////////////////////////////////////////
    第一步当然是读数据:
    我们将要做的是通过读取包来读取整个视频流,然后把它解码成帧,最后转换格式并且保存。
    while(av_read_frame(pFormatCtx, &packet)>=0) { //读数据
    if(packet.stream_index==videoStream){ //判断是否视频流
    avcodec_decode_video(pCodecCtx,pFrame, &frameFinished,
    packet.data, packet.size); //解码
    if(frameFinished) {
    img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,(AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,pCodecCtx->height);//转换
    SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height, i); //保存数据
    av_free_packet(&packet); //释放
    av_read_frame()读取一个包并且把它保存到AVPacket结构体中。这些数据可以在后面通过av_free_packet()来释 放。函数avcodec_decode_video()把包
    转换为帧。然而当解码一个包的时候,我们可能没有得到我们需要的关于帧的信息。因此,当我们得 到下一帧的时候,avcodec_decode_video()为我们设
    置了帧结束标志frameFinished。最后,我们使用 img_convert()函数来把帧从原始格式(pCodecCtx->pix_fmt)转换成为RGB格式。要记住,你可以把一个
    AVFrame结构体的指针转换为AVPicture结构体的指针。最后,我们把帧和高度宽度信息传递给我们的SaveFrame函数。
    到此解码完毕,显示过程使用SDL完成考虑到我们以后会使用firmware进行显示操作,SDL忽略不讲。
    音视频同步
    DTS(解码时间戳)和PTS(显示时间戳)
    当我们调用av_read_frame()得到一个包的时候,PTS和DTS的信息也会保存在包中。但是我们真正想要的PTS是我们刚刚解码出来的 原始帧 的PTS,这样我
    们才能知道什么时候来显示它。然而,我们从avcodec_decode_video()函数中得到的帧只是一个AVFrame,其中并 没有包含有用的PTS值(注意:AVFrame并
    没有包含时间戳信息,但当我们等到帧的时候并不是我们想要的样子)。。我们保存一帧的第一个包的PTS: 这将作为整个这一帧的PTS。我们 可以通过函
    数avcodec_decode_video()来计算出哪个包是一帧的第一个包。怎样实现呢?任何时候当一个包开始一帧的时 候,avcodec_decode_video()将调用一个函数
    来为一帧申请一个缓冲。当然,ffmpeg允许我们重新定义那个分配内存的函数。计算前 一帧和现在这一帧的时间戳来预测出下一个时间戳的时间。同时,我
    们需要同步视频到音频。我们将设置一个音频时间audioclock;一个内部值记录了我 们正在播放的音频的位置。就像从任意的mp3播放器中读出来的数字一
    样。既然我们把视频同步到音频,视频线程使用这个值来算出是否太快还是太慢。

  • 相关阅读:
    根据坐标经纬度计算两点之间的距离
    C# 获取类名
    Post、Get请求
    Image和Base64相互转换
    Html checkbox全选
    .NET Core 中间件
    C# DataTable 用法
    imshow(A,[])和imshow(A)的区别
    Log-spectral distance
    CUDA
  • 原文地址:https://www.cnblogs.com/wenrenhua08/p/3937647.html
Copyright © 2011-2022 走看看