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

2020-03-27 12:25:30 浏览数 (1)

一、实现逻辑:

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

ffmpeg解码流程:

ffmpeg解码流程

SDL显示流程:

SDL显示流程

二、iOS里的特殊适配

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

  1. 修改main代码,在main里实现核心逻辑
  2. 删掉appdelegate.h/m文件
代码语言:javascript复制
#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;
//    }
//}

三、解码&显示代码

代码语言:javascript复制
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实现视频流播放

0 人点赞