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()android

FfmpegEncoder.hide

/*
 * 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'\n", 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\n", 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\n", 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\n", 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: ui

這一步註冊庫中含有的全部可用的文件格式和編碼器,這樣當打開一個文件時,它們纔可以自動選擇相應的文件格式和編碼器。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播放器中讀出來的數字一
樣。既然咱們把視頻同步到音頻,視頻線程使用這個值來算出是否太快仍是太慢。
編碼

相關文章
相關標籤/搜索