音视频技术(5)-iOS ffmpeg+SDL播放视频

  • 2020 年 3 月 27 日
  • 笔记

一、实现逻辑:

ffmpeg解封装->ffmpeg解码->SDL循环一帧帧显示

ffmpeg解码流程:

ffmpeg解码流程

SDL显示流程:

SDL显示流程

二、iOS里的特殊适配

参照SDL官网文档说明,iOS上使用SDL显示图像,需要修改main入口,SDL有自己的appdelegate实现,

  1. 修改main代码,在main里实现核心逻辑
  2. 删掉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运行效果

四、问题

  1. 解码过程中出现报错:avformat_close_input(&pFormatCtx) 报错 error for object 0x9: pointer being freed was not allocated,找不到原因
  2. 没研究怎么切换横竖屏

五、参考 FFmpeg+SDL2实现视频流播放