1. 程式人生 > >FFMPEG記憶體操作(二)從記憶體中讀取數及資料格式的轉換

FFMPEG記憶體操作(二)從記憶體中讀取數及資料格式的轉換

   相關部落格列表: 

    在雷神的《最簡單的基於FFmpeg的記憶體讀寫例子(記憶體播放器)》中,它是設計回撥函式從輸入檔案中讀取資料。與FFMPEG 官方給出的avio_reading.c不同的是,雷神給的例子是當需要資料的時候,回撥函式才去從輸入檔案讀取資料,而avio_reading.c 則是直接全部資料讀取到記憶體中待後面處理。

    在我的這個例項中,我是將讀取到的輸入檔案解碼成YUV420P資料格式。同時可以通過設定不同的資料格式和尺寸實現輸出影象的拉伸縮小或是資料格式的裝換。

/*=============================================================================   
#     FileName: ffmpeg_mem_read.c   
#         Desc: an example of ffmpeg read from memory
#       Author: licaibiao   
#   LastChange: 2017-03-21    
=============================================================================*/  
#include <stdio.h>

#define __STDC_CONSTANT_MACROS
#include "avcodec.h"
#include "avformat.h"
#include "swscale.h"

//#define QUARTER_SHOW   

char *input_name = "cuc60anniversary_start.ts";

FILE *fp_open = NULL;

int read_buffer(void *opaque, uint8_t *buf, int buf_size){
	if(!feof(fp_open)){
		int true_size=fread(buf,1,buf_size,fp_open);
		return true_size;
	}else{
		return -1;
	}
}

int main(int argc, char* argv[])
{
	AVFormatContext	*pFormatCtx;
    AVCodecContext	*pCodecCtx;
    AVCodec			*pCodec;
    AVIOContext     *avio;
    AVFrame	        *pFrame;
    AVFrame         *pFrameYUV;
    AVPacket        *packet;
    
    struct SwsContext *img_convert_ctx;

    int             videoindex;
	int				i;
    int             ret;
    int             got_picture;
    int             y_size;
    unsigned char   *aviobuffer;
    FILE            *fp_yuv;


	//fp_open = fopen(argv[1],"rb+");
	fp_open = fopen(input_name, "rb+");
    fp_yuv  = fopen("output.yuv", "wb+");     

	av_register_all();
	pFormatCtx = avformat_alloc_context();

	aviobuffer=(unsigned char *)av_malloc(32768);
	avio = avio_alloc_context(aviobuffer, 32768,0,NULL,read_buffer,NULL,NULL);

    /* Open an input stream and read the header. */
    pFormatCtx->pb = avio;
	if(avformat_open_input(&pFormatCtx,NULL,NULL,NULL)!=0){
		printf("Couldn't open input stream.\n");
		return -1;
	}

    /* Read packets of a media file to get stream information. */
	if(avformat_find_stream_info(pFormatCtx,NULL)<0){
		printf("Couldn't find stream information.\n");
		return -1;
	}
    
	videoindex = -1;
	for(i=0; i<pFormatCtx->nb_streams; i++) {
		if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
			videoindex=i;
			break;
		}
    } 
    
	if(videoindex==-1){
		printf("Didn't find a video stream.\n");
		return -1;
	}

	av_dump_format(pFormatCtx, 0, input_name, 0);

    /* Find a registered decoder with a matching codec ID */
	pCodecCtx = pFormatCtx->streams[videoindex]->codec;
	pCodec    = avcodec_find_decoder(pCodecCtx->codec_id);   
	if(pCodec==NULL){
		printf("Codec not found.\n");
		return -1;
	}

    /* Initialize the AVCodecContext to use the given AVCodec */
	if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
		printf("Could not open codec.\n");
		return -1;
	}
	
	pFrame    = av_frame_alloc();
	pFrameYUV = av_frame_alloc();
	uint8_t *out_buffer=(uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
	avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

    /*  Allocate and return an SwsContext. */
    /* srcW:源影象的寬
     * srcH:源影象的高
     * srcFormat:源影象的畫素格式
     * dstW:目標影象的寬
     * dstH:目標影象的高
     * dstFormat:目標影象的畫素格式
     * flags:設定影象拉伸使用的演算法
    */
#ifndef QUARTER_SHOW
	img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); 
#else
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width/2, pCodecCtx->height/2, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); 
    printf("out frame  width = %d, height = %d \n", pCodecCtx->width/2,pCodecCtx->height/2);
#endif
	packet = (AVPacket *)av_malloc(sizeof(AVPacket));
	while(av_read_frame(pFormatCtx, packet) >= 0){
		if(packet->stream_index == videoindex){
			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
			if(ret < 0){
				printf("Decode Error.\n");
				return -1;
			}
			if(got_picture){
                /* Scale the image slice in srcSlice and put the resulting scaled slice in the image in dst. 影象處理函式 */
                /* c             the scaling context previously created with  sws_getContext()
                 * srcSlice      the array containing the pointers to the planes of the source slice
                 * srcStride     the array containing the strides for each plane of
                 * srcSliceY     the position in the source image of the slice to process, that is the number (counted starting from
                                 zero) in the image of the first row of the slice 輸入影象資料的第多少列開始逐行掃描,通常設為0
                 * srcSliceH     the height of the source slice, that is the number of rows in the slice 為需要掃描多少行,通常為輸入影象資料的高度
                 * dst           the array containing the pointers to the planes of the destination image
                 * dstStride     the array containing the strides for each plane of the destination image
                 */  
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
               
#ifndef QUARTER_SHOW
               // printf("pFrameYUV->height   = %d\n ",pFrameYUV->height);
               // printf("pFrameYUV->width    = %d\n ", pFrameYUV->width);
               // printf("pFrameYUV->pkt_size = %d\n ",pFrameYUV->pkt_size);

                y_size = pCodecCtx->width*pCodecCtx->height; 
				fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y 
				fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U
				fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V
#else
                for(i=0; i<pCodecCtx->height/2; i++){
                    fwrite(pFrameYUV->data[0]+pCodecCtx->width * i ,1,pCodecCtx->width/2,fp_yuv); 
                }
                for(i=0; i<pCodecCtx->height/2; i = i + 2){
                    fwrite(pFrameYUV->data[1]+pCodecCtx->width * i/4 ,1,pCodecCtx->width/4,fp_yuv);
                }

                for(i=0; i<pCodecCtx->height/2; i = i + 2 ){             
                    fwrite(pFrameYUV->data[2]+pCodecCtx->width * i/4 ,1,pCodecCtx->width/4,fp_yuv); 
                }    
#endif             
                    
			}
		}
		av_free_packet(packet);
	}
	sws_freeContext(img_convert_ctx);

    fclose(fp_yuv);
	fclose(fp_open);
	av_free(out_buffer);
	av_free(pFrameYUV);
	avcodec_close(pCodecCtx);
	avformat_close_input(&pFormatCtx);

	return 0;
}

    正常操作是將輸入檔案解碼成YUV資料。

    當我們對sws_getContext  設定不同引數的時候,我們可以得到不同的輸出資料。在這裡需要注意幾點:

    (1)out_buffer 的大小要跟著sws_getContext  引數的設定而改變,如果out_buffer分配得比輸出資料小,會出現記憶體溢位問題。

    (2)sws_scale 函式中,我們一般設定從輸入資料的第0行開始掃描,掃描的高度(也就是行數)一般是輸入資料的高度。

    (3)得到的YUV資料是儲存在pFrameYUV->data 的三個分量裡,需要分開讀取。

舉例:

    在sws_getContext 中設定輸出格式的寬和高都是輸入格式的一半。正常的顯示應該是影象變為了原來的1/4大小。如果我們還是按原圖尺寸提取輸出資料:

     y_size = pCodecCtx->width*pCodecCtx->height; 
    fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y 
    fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U
    fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V
得到的影象將會是:    如上圖,影象顯示是正常的,但同時也提取了很多的無用資料,實際影象並沒有縮小到原來的1/4

    所以我們需要自己對輸出資料再重組:

                for(i=0; i<pCodecCtx->height/2; i++){
                    fwrite(pFrameYUV->data[0]+pCodecCtx->width * i ,1,pCodecCtx->width/2,fp_yuv); 
                }
                for(i=0; i<pCodecCtx->height/2; i = i + 2){
                    fwrite(pFrameYUV->data[1]+pCodecCtx->width * i/4 ,1,pCodecCtx->width/4,fp_yuv);
                }

                for(i=0; i<pCodecCtx->height/2; i = i + 2 ){             
                    fwrite(pFrameYUV->data[2]+pCodecCtx->width * i/4 ,1,pCodecCtx->width/4,fp_yuv); 
                } 
    分別重新提取了Y U V 三個分量上的資料,得到影象如下:

    在讀取輸出資料的時候,如果發現讀取的輸出資料顯示有問題,最好是使用UltraEdit 工具對某幀資料進行分析,先確認輸出的一幀資料是否正常,然後才進行視訊格式的轉換。比如出現下面的這種情況

    從影象中大概能分析出來,Y分量資料是正常的,影象右半邊的UV分量可能丟失。使用UltraEdit檢視原始資料,如下圖。我們可以看到從地址0xe150開始有一段是全0的資料,因此我們可以到我們的程式中去檢查是否UV分量讀取錯誤。

    總結:我們直接使用sws_getContext和sws_scale 可以直接對影象進行拉伸和縮放,同時可以進行資料格式的轉換,只需要設定sws_getContext 就可以了,非常簡單。但是需要注意,輸出的資料需要我們自己重新組合,否則讀取到的輸出資料很有可能出錯。