昨天那個例子,在 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;
}