音视频技术(5)-iOS ffmpeg+SDL播放视频
- 2020 年 3 月 27 日
- 笔记
一、实现逻辑:
ffmpeg解封装->ffmpeg解码->SDL循环一帧帧显示
ffmpeg解码流程:

ffmpeg解码流程
SDL显示流程:

SDL显示流程
二、iOS里的特殊适配
参照SDL官网文档说明,iOS上使用SDL显示图像,需要修改main入口,SDL有自己的appdelegate实现,
- 修改main代码,在main里实现核心逻辑
- 删掉appdelegate.h/m文件
#import <UIKit/UIKit.h> //#import "AppDelegate.h" #import <SDL.h> #import "AudioPlayerSDL2.h" int main(int argc, char * argv[]) { @autoreleasepool { // SDL_Init((SDL_INIT_AUDIO | SDL_INIT_TIMER)); sdlplay(); return 0; // return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } //extern int SDL_main(int argc, char * argv[]) //{ // @autoreleasepool { // RootViewController *viewController = [[RootViewController alloc] initWithNibName:@"ViewController" bundle:nil]; // [UIApplication sharedApplication].keyWindow.rootViewController = viewController; // [[UIApplication sharedApplication].keyWindow makeKeyAndVisible]; // return 0; // } //}
三、解码&显示代码
int thread_exit = 0; int thread_pause = 0; int video_refresh_thread(void *data){ thread_exit = 0; thread_pause = 0; while (!thread_exit) { if(!thread_pause){ SDL_Event event; event.type = REFRESH_EVENT; printf("while pushn"); SDL_PushEvent(&event); } SDL_Delay(40); //每40ms发一次刷新界面的消息 } printf("thread_exit:%dn", thread_exit); printf("thread_pause:%dn", thread_pause); thread_exit = 0; thread_pause = 0; SDL_Event event; event.type = BREAK_EVENT; SDL_PushEvent(&event); printf("push eventn"); return 0; } int sdlplay(){ printf("sdlplayn"); int ret = -1; // file path // NSString *mp4file = [NSString stringWithFormat:@"resource.bundle/war3end.mp4"]; NSString *mp4file = [NSString stringWithFormat:@"resource.bundle/sintel.mov"]; NSString *mp4pth = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:mp4file]; AVFormatContext *pFormatCtx = NULL; int i, videoStream; AVCodecParameters *pCodecParameters = NULL; AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVFrame *pFrame = NULL; AVPacket packet; SDL_Rect rect; Uint32 pixformat; SDL_Window *win = NULL; SDL_Renderer *renderer = NULL; SDL_Texture *texture = NULL; SDL_Thread *video_thread; SDL_Event event; //默认窗口大小 int w_width = 640; int w_height = 480; //SDL初始化 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not initialize SDL - %sn", SDL_GetError()); return ret; } // 打开输入文件 if (avformat_open_input(&pFormatCtx, [mp4pth UTF8String], NULL, NULL) != 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open video file!"); goto __FAIL; } //找到视频流 videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (videoStream == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Din't find a video stream!"); goto __FAIL;// Didn't find a video stream } // 流参数 pCodecParameters = pFormatCtx->streams[videoStream]->codecpar; //获取解码器 pCodec = avcodec_find_decoder(pCodecParameters->codec_id); if (pCodec == NULL) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unsupported codec!n"); goto __FAIL; // Codec not found } // 初始化一个编解码上下文 pCodecCtx = avcodec_alloc_context3(pCodec); if (avcodec_parameters_to_context(pCodecCtx, pCodecParameters) != 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't copy codec context"); goto __FAIL;// Error copying codec context } // 打开解码器 if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open decoder!n"); goto __FAIL; // Could not open codec } // Allocate video frame pFrame = av_frame_alloc(); w_width = pCodecCtx->width; w_height = pCodecCtx->height; //创建窗口 win = SDL_CreateWindow("Media Player", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w_width, w_height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if (!win) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create window by SDL"); goto __FAIL; } //创建渲染器 renderer = SDL_CreateRenderer(win, -1, 0); if (!renderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Renderer by SDL"); goto __FAIL; } pixformat = SDL_PIXELFORMAT_IYUV;//YUV格式 // 创建纹理 texture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING, w_width, w_height); SDL_CreateThread(video_refresh_thread, "Video Thread", NULL); printf("create thread n"); int count = 0; int sret = -1; for (;;) { SDL_WaitEvent(&event); if(event.type == REFRESH_EVENT){ while (1) { if(av_read_frame(pFormatCtx, &packet) < 0){ thread_exit = 1; } if(packet.stream_index == videoStream){ break; } } if (packet.stream_index == videoStream){ avcodec_send_packet(pCodecCtx, &packet); while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) { SDL_UpdateYUVTexture(texture, NULL, pFrame->data[0], pFrame->linesize[0], pFrame->data[1], pFrame->linesize[1], pFrame->data[2], pFrame->linesize[2]); rect.x = 0; rect.y = 0; rect.w = pCodecCtx->width; rect.h = pCodecCtx->height; SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, &rect); SDL_RenderPresent(renderer); } av_packet_unref(&packet); } else if(event.type == SDL_KEYDOWN){ if(event.key.keysym.sym == SDLK_SPACE){ thread_pause = !thread_pause; } if(event.key.keysym.sym == SDLK_ESCAPE){ thread_exit = 1; } } else if(event.type == SDL_QUIT){ thread_exit = 1; } else if(event.type == BREAK_EVENT){ break; } } } __QUIT: ret = 0; __FAIL: printf("freen"); // Free the YUV frame if (pFrame) { av_frame_free(&pFrame); } // Close the codec if (pCodecCtx) { avcodec_close(pCodecCtx); } if (pCodecParameters) { avcodec_parameters_free(&pCodecParameters); } // Close the video file if (pFormatCtx) { avformat_close_input(&pFormatCtx); } if (win) { SDL_DestroyWindow(win); } if (renderer) { SDL_DestroyRenderer(renderer); } if (texture) { SDL_DestroyTexture(texture); } SDL_Quit(); return ret; }

demo运行效果
四、问题
- 解码过程中出现报错:avformat_close_input(&pFormatCtx) 报错 error for object 0x9: pointer being freed was not allocated,找不到原因
- 没研究怎么切换横竖屏
五、参考 FFmpeg+SDL2实现视频流播放