一個簡單的視頻播放器(基於FFMPEG4.0+SDL2.0.8,在Ubuntu 14.04下開發驗證) - 續2

昨天那個例子,在 Ubuntu 14.04下播放視頻時,有個問題,有播放幾秒後,畫面就變成黑白的了。剛開始懷疑是UV數據丟失,不過在將YUV數據輸出到文件,用YUV Player Deluxe播放,畫面色彩正常着。

今天在主程序中新起了一個SDL Thread,發現畫面就好了,奇怪的很。問題雖然已經解決,但原因還沒找到。應該是和SDL Window的某些特性相關,視頻解碼和生成YUV是沒有問題的。 

修改點:

1. 新建一個獨立的SDL Thread,每40毫秒給主線程發送一個event;

2. 在主線線程主循環中,監聽event,收到event後,再做對應動作,包括按鍵退出、暫停、畫面刷新等。

開發環境同上一篇,Makefile不變,代碼如下:

/*
 * A simple player with FFMPEG4.0 and SDL2.0.8.
 * Only support video decoder, not support audio and subtitle.
 * Created by LiuWei@20180524
 * Reference: https://blog.csdn.net/leixiaohua1020/article/details/38868499
 *
 * Modifications:
 * 20180525: add key event loop
 *   
 */
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL2/SDL.h>
#include <libavutil/imgutils.h>

/* Program will quit if quit is SDL_TRUE */
SDL_bool quit  = SDL_FALSE; 
SDL_bool pause =  SDL_FALSE;

/* SDL Event Type, defined by myself. Loop every 40ms, to notify SDL Window refresh with a new frame */
#define FRAME_REFRESH_EVENT (SDL_USEREVENT+1)
static int frame_refresh_thread(void *arg)
{
	while(!quit) {
		if(!pause) {
			SDL_Event event;
			event.type = FRAME_REFRESH_EVENT;
			SDL_PushEvent(&event);
		}
		SDL_Delay(40);
	}
	return 0;
}

int main(int argc, char *argv[])
{
	/* AVFormatContext contains:
	 * 1. iformat(AVInputFormat) : It's either autodetected or set by user.
	 * 2. oformat(AVOutputFormat): always set by user for output.
	 * 3. streams "array" of AVStreams: describe all elementary streams stored in the file.
	 *    AVStreams are typically referred to using their index in this array. */
	/* We can keep pFormatCtx as NULL. avformat_open_input() will allocate for it. */
	AVFormatContext *pFormatCtx = NULL;

	int             i, videoindex, audioindex, titleindex;
	AVCodecContext  *pCodecCtx;
	AVCodec         *pCodec;
	AVFrame         *pFrame, *pFrameYUV;
	AVStream	    *avStream;
	AVPacket        *packet;
	unsigned char   *out_buffer;
        int             ret;
	struct SwsContext *img_convert_ctx;

	char filepath[256] = {0};
	/* SDL */
	int screen_w, screen_h = 0;
	SDL_Window   *screen;
	SDL_Renderer *sdlRenderer;
	SDL_Texture  *sdlTexture;
	SDL_Rect     sdlRect;
	SDL_Event    event;
	SDL_Thread   *refresh_thread;

	/* Parse input parameter. The right usage: ./myplayer xxx.mp4 */
	if(argc < 2 || argc > 2) {
		printf("Too few or too many parameters. e.g. ./myplayer xxx.mp4\n");
		return -1;
	}
	if(strlen(argv[1]) >= sizeof(filepath)) {
		printf("Video path is too long, %d bytes. It should be less than %d bytes.\n",
			strlen(argv[1]), sizeof(filepath));
		return -1;
	}
	strncpy(filepath, argv[1], sizeof(filepath));

	/* Open the specified file(autodetecting the format) and read the header, exporting the information
	 * stored there into AVFormatContext. The codecs are not opened. 
	 * The stream must be closed with avformat_close_input(). */
	ret = avformat_open_input(&pFormatCtx, filepath, NULL, NULL);
	if(ret != 0) {
		printf("[error]avformat_open_input: %s\n", av_err2str(ret));
		return -1;
	}

	/* Some formats do not have a header or do not store enough information there, so it it recommended 
         * that you call the avformat_find_stream_info() which tries to read and decode a few frames to find 
         * missing information. 
         * This is useful for file formats with no headers such as MPEG. */
	ret = avformat_find_stream_info(pFormatCtx, NULL);
	if(ret < 0) {
		printf("[error]avformat_find_stream_info: %s\n", av_err2str(ret));
		
		avformat_close_input(&pFormatCtx);
		return -1;
	}

	/* Find out which stream is video, audio, and subtitle. */
	videoindex = audioindex = titleindex = -1;
	for(i = 0; i < pFormatCtx->nb_streams; i++) {
		avStream = pFormatCtx->streams[i];
		switch(avStream->codecpar->codec_type) {
			case AVMEDIA_TYPE_VIDEO:
				videoindex = i;
				break;
			case AVMEDIA_TYPE_AUDIO:
				audioindex = i;
				break;
			case AVMEDIA_TYPE_SUBTITLE:
				titleindex = i;
				break;
		}
	}
	if(videoindex == -1) {
		printf("Didn't find a video stream.\n");
		avformat_close_input(&pFormatCtx);
		return -1;
	}

	/* Open video codec */
	pCodecCtx = avcodec_alloc_context3(NULL);   /* It should be freed with avcodec_free_context() */
	if(!pCodecCtx) {
		printf("[error]avcodec_alloc_context3() fail\n");
		avformat_close_input(&pFormatCtx);
		return -1;
	}
	ret = avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoindex]->codecpar);
	if(ret < 0) {
		printf("[error]avcodec_parameters_to_context: %s\n", av_err2str(ret));
		avcodec_free_context(&pCodecCtx);
		avformat_close_input(&pFormatCtx);
		return -1;
	}
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
	if(pCodec == NULL) {
		printf("Video Codec not found.\n");
		avcodec_free_context(&pCodecCtx);
		avformat_close_input(&pFormatCtx);		
		return -1;
	}
	ret = avcodec_open2(pCodecCtx, pCodec, NULL);
	if(ret < 0) {
		printf("[error]avcodec_open2: %s\n", av_err2str(ret));
		avcodec_free_context(&pCodecCtx);
		avformat_close_input(&pFormatCtx);		
		return -1;
	}

	/* Output info */
	printf("-------------File Information-------------\n");
	av_dump_format(pFormatCtx, 0, filepath, 0);
	printf("------------------------------------------\n");

	/* SDL */
	pFrame    = av_frame_alloc();   /* av_frame_free(&pFrame); */
	pFrameYUV = av_frame_alloc();
	/* av_image_get_buffer_size() returns the size in bytes of the amount of data required to store an image with the given parameters. */
	out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(
					AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, 
					AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
	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);

	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) {
		printf("Could not initialize SDL - %s\n", SDL_GetError());		

		av_free(out_buffer);
		av_frame_free(&pFrame);	
		av_frame_free(&pFrameYUV);
		avcodec_free_context(&pCodecCtx);
		avformat_close_input(&pFormatCtx);		
		return -1;	
	}

	screen_w = pCodecCtx->width;
	screen_h = pCodecCtx->height;
	screen   = SDL_CreateWindow("Simplest ffmpleg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
				screen_w, screen_h, SDL_WINDOW_RESIZABLE);  //SDL_WINDOW_OPENGL
	if(!screen) {
		printf("SDL: could not create window - %s\n", SDL_GetError());

		av_free(out_buffer);
		av_frame_free(&pFrame);	
		av_frame_free(&pFrameYUV);
		avcodec_free_context(&pCodecCtx);
		avformat_close_input(&pFormatCtx);		
		return -1;	
	}
	
	sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
	sdlTexture  = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, 
					pCodecCtx->width, pCodecCtx->height);
	sdlRect.x = 0;
	sdlRect.y = 0;
	sdlRect.w = screen_w;
	sdlRect.h = screen_h;
	/* SDL End */

	/* For decoding, call avcodec_send_packet() to give the decoder raw compressed data in an AVPacket. 
         *		 call avcodec_receive_frame() in a loop until AVERROR_EOF is returned. 
	 * 		 On success, it will return an AVFrame containing uncompressed audio or video data. */
	packet = (AVPacket *)av_malloc(sizeof(AVPacket));
	av_init_packet(packet);
	
	refresh_thread = SDL_CreateThread(frame_refresh_thread, NULL, NULL);
	while(!quit) {	
		if(SDL_WaitEvent(&event) != 1)
			continue;

		switch(event.type) {
		case SDL_KEYDOWN:  
			if(event.key.keysym.sym == SDLK_ESCAPE)  
				quit = SDL_TRUE;
			if(event.key.keysym.sym == SDLK_SPACE)  
				pause = !pause;			
			break;

		case SDL_QUIT:    /* Window is closed */
			quit = SDL_TRUE;
			break;

		case FRAME_REFRESH_EVENT:  /* refresh window with a new frame */
			if(av_read_frame(pFormatCtx, packet) < 0) {  /* No more packets */
				quit = SDL_TRUE;
				break;
			}
			// Only handle video packet
			if(packet->stream_index != videoindex) {
				av_packet_unref(packet);
				av_init_packet(packet);
				break;		
			}
	
			/* @return 0 on success
			 *    AVERROR(EAGAIN): input is not accepted in the current state - user must read output with avcodec_receive_frame()
			 *        AVERROR_EOF: the decoder has been flushed, and no new packets can be sent to it 
			 *    AVERROR(EINVAL): codec not opened, it is an encoder, or requires flush 
			 *    AVERROR(ENOMEM): failed to add packet to internal queue, or similar */
			ret = avcodec_send_packet(pCodecCtx, packet);
			if( ret != 0 ) {
				av_packet_unref(packet);
				av_init_packet(packet);
				break;
			}
	
			/* Got frame */
			do {
				/* @return 0 on success, a frame was returned
				 *     AVERROR(EAGAIN): output is not available in this state - user must try to send new input
				 *         AVERROR_EOF: the decoder has been fully flushed, and there will be no more output frames
				 *     AVERROR(EINVAL): codec not opened, or it is an encoder
				 *     other negative values: legitimate decoding errors */
				ret = avcodec_receive_frame(pCodecCtx, pFrame);
				if(ret < 0) 
					break;
				else if(ret == 0) {  /* Got a frame successfully */
					sws_scale(img_convert_ctx, (const unsigned char * const *)pFrame->data, pFrame->linesize, 0, 
						pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
					/* SDL */
					SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
						pFrameYUV->data[0], pFrameYUV->linesize[0],
						pFrameYUV->data[1], pFrameYUV->linesize[1], 
						pFrameYUV->data[2], pFrameYUV->linesize[2]);
					SDL_RenderClear(sdlRenderer);
					SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
					SDL_RenderPresent(sdlRenderer);
					/* SDL End */
				} else if(ret == AVERROR_EOF) {
					avcodec_flush_buffers(pCodecCtx);
					break;
				}
			} while(ret != AVERROR(EAGAIN));  

			av_packet_unref(packet);
			av_init_packet(packet);

			break;
		} /* End of switch */
	} /* End of while(!quit) */

	sws_freeContext(img_convert_ctx);
	SDL_DestroyRenderer(sdlRenderer);
	SDL_Quit();
	
	av_free(packet);
	av_free(out_buffer);
	av_frame_free(&pFrame);	
	av_frame_free(&pFrameYUV);
	avcodec_close(pCodecCtx);
	avcodec_free_context(&pCodecCtx);
	avformat_close_input(&pFormatCtx);

	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章