初学者 ffmpeg + SDL2.0 安装与应用概要点

来源:互联网 发布:闲鱼钓鱼网站源码 编辑:程序博客网 时间:2024/06/10 08:46
# ffmpeg - 2.8.6 与 SDL2.* 学习笔记
一、源代码下载和安装 
   1. 利用Git 工具从 https://github.com/kewlbear/FFmpeg-iOS-build-script 下载自动化编译ffmpeg脚本
       a. 可以适当修改 TARGET 版本,如:7.0
       b. 在命令行中执行sh脚本,等待自动化下载ffmpeg 源代码,并编译 IOS 版本的 frameword
       c. 最终将会得到【FFmpeg-iOS】目录,里边为库文件,和头文件
   2. 利用Git 工具从 https://github.com/manifest/sdl-ios-framework.git 下载自动化编译脚本
       a. 事先 安装好 hg、git、svn 这些工具
       b. 安装好 rubygems、colorize(sudo gem install colorize)
       c. 在命令行环境下 进入 【dl-ios-framework】目录,执行rake,则自动编译构建SDL2.frameword


二、ffmpeg 视频解码 简单开始,以及关键流程
包含头文件如下:
#import "libavutil/avutil.h"
#import "libavutil/opt.h"
#import "libavutil/imgutils.h"    //图像工具
#import <libavcodec/avcodec.h>
#import <libavformat/avformat.h>


    1. 新建工程,并将ffmpeg 静态库加入到工程中,引用 include 头文件,注意修改工程配置 头文件搜索目录路径
    CoreMotion,CoreMedia,QuartzCore, MediaPlayer, GameController, OpenGLES, AudioToolBox, AVFoundation, VideoToolBox, libz, libiconv, libbz, Foundation, CoreGraphic, UIKit, MobileCoreService, ImageIO


    2. 定义 ffmpeg av解码上下文变量,示例代码如下:
        AVFormatContext *pFormatCtx = NULL; // 视频文件上下文
    AVCodecContext  *pCodecCtx = NULL;  // 视频解码器上下文
    AVCodec         *pCodec = NULL;     // 视频解码器结构
    int              videoStream;       // 视频流索引


    3. 注册所有视频格式和解码器
        av_register_all();


    4. 打开本地视频文件
        if(avformat_open_input(&pFormatCtx, "视频文件全路径", NULL, NULL)!=0)
        return -1; // Couldn't open file


    5. 检索视频流信息
    if(avformat_find_stream_info(pFormatCtx, NULL)<0)
        return -1; // Couldn't find stream information


    6. 打印视频流信息(可有可无)
        av_dump_format(pFormatCtx, 0, "视频文件全路径", 0);


    7. 查找视频流索引位置
    for(int index = 0; index < pFormatCtx->nb_streams; index++)
        if( pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
            videoStream = i; // 找到视频流索引位置
            break;
        }


    8. 获取视频解码上下文环境
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;


    9. 获取视频解码器结构
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);


    10. 初始化视频解码帧结构和显示帧结构
    AVFrame * pDecodecFrame = av_frame_alloc();
    AVFrame * pDisplayFrame = av_frame_alloc();


    11. 初始化视频帧图像缩放上下文环境(网上的资料比较老,PIX_FMT_YUV420P 在新版ffmpeg中为 AV_PIX_FMT_YUV420P)
    SwsContext * pSwsContext = sws_getContext (
      pCodecCtx->width,
      pCodecCtx->height,
      pCodecCtx->pix_fmt,
      pCodecCtx->width,
      pCodecCtx->height,
      AV_PIX_FMT_YUV420P,
      SWS_BILINEAR,
      NULL,
      NULL,
      NULL );


    12. 为显示帧结构申请内存空间
    int numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
    uint8_t* buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); // 必须用 av_malloc 内存空间字节对齐
    avpicture_fill((AVPicture *)pFrameYUV, buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);


    13. 循环读取 视频帧包结构
    while(av_read_frame(pFormatCtx, &packet) >= 0)


    14. 判断当前取到的包数据是否为视频数据 
    if(packet.stream_index == videoStream)


    15. 解码报数据为视频帧
    avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);


    16. 解码报数据后,组装成完整帧结构时候,就可以显示该帧图像了
    if(frameFinished) {
                
                sws_scale (
                 sws_ctx,
                 (uint8_t const * const *)pDecodecFrame->data,
                 pDecodecFrame->linesize,
                 0,
                 pCodecCtx->height,
                 pDisplayFrame->data,
                 pDisplayFrame->linesize
                 );
                // 此时可以显示该帧图像了,如利用SDL 也可以接其他显示框架
            }


    17. 释放包内存空间
    av_free_packet(&packet); // 在读流数据循环中


    18. 释放其他内存空间方法
    av_free (buffer); // 释放显示帧内存数据空间
    av_frame_free( &pDecodecFrame );
    av_frame_free( &pDisplayFrame );
    avcodec_close( pCodecCtx );
    avcodec_free_context (pCodecCtx);
    avformat_close_input(&pFormatCtx);


    19. ffmpeg 简单应用总结
    ffmpeg 为我们提供了良好的用户接口,来支持视频文件的读取与解码等功能,上述只是指出了ffmpeg 在视频播放时候的关键点。其中,还需要视频播放帧缓冲队列,以及流缓冲,还需要时间派发线程来支持用户的交互以及播放流程的控制;音频的播放过程基本上与视频的播放过程一致。此外还需要解决一下音视频同步的问题。


三、ffmpeg 音频解码 简单开始,以及关键流程
    包含头文件如下:
    #import <libavcodec/avcodec.h>
#import <libavformat/avformat.h>
#import <libswresample/swresample.h>
#import "libavutil/samplefmt.h"


1.  定义音频解码上线文
    AVCodecContext*pAudioCodecCtx;
    AVCodec *pAudioCodec;


    2.  查找音频流索引
    audioStream=-1;
    for(i=0; i < pFormatCtx->nb_streams; i++)
        if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){
            audioStream=i;
            break;
        }


    3.  获取音频流上下文环境
    pAudioCodecCtx = pFormatCtx->streams[audioStream]->codec


    4.  获取音频解码器结构
    pAudioCodec = avcodec_find_decoder(pAudioCodecCtx->codec_id);


    5.  打开音频解码器
    if(avcodec_open2(pAudioCodecCtx, pAudioCodec,NULL)<0){
        printf("Could not open codec.\n");
        return -1;
    }


    6. 初始化音轨结构
    SDL_AudioSpec wanted_spec;
    wanted_spec.freq = 44100;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = 2;
    wanted_spec.silence = 0;
    wanted_spec.samples = 1152; //每帧音频大小, 注意这里很重要,1152 = mp3; 1024 = acc
    wanted_spec.callback = fill_audio; // 音频缓存区


    7. 打开音频设备
    if (SDL_OpenAudio(&wanted_spec, NULL)<0){
        printf("can't open audio.\n");
        return -1;
    }


    8. 创建每帧音频内存缓冲区
    uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO; // 音频通道布局,流式布局
    //nb_samples: AAC-1024 MP3-1152
    int out_nb_samples = pAudioCodecCtx->frame_size; // 输出音频帧大小
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16; // 音频采样格式
    int out_sample_rate = 44100; // 采样率
    int out_channels = av_get_channel_layout_nb_channels(out_channel_layout); // 根据通道布局结构获取通道数目
    // 创建一帧音频缓冲区大小
    int out_buffer_size = av_samples_get_buffer_size(NULL,
    out_channels ,
    out_nb_samples,
    out_sample_fmt, 
    1);
    #define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio
    out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE*2);


    9. 创建音频解码器解码上下文环境
    //FIX:Some Codec's Context Information is missing
    in_channel_layout = av_get_default_channel_layout(pAudioCodecCtx->channels);
    //Swr
    au_convert_ctx = swr_alloc();
    au_convert_ctx = swr_alloc_set_opts(
    au_convert_ctx,
    out_channel_layout, 
    out_sample_fmt, 
    out_sample_rate,
                                      in_channel_layout,
                                      pAudioCodecCtx->sample_fmt , 
                                      pAudioCodecCtx->sample_rate,
                                      0, 
                                      NULL);
    swr_init(au_convert_ctx);


    10. 音频读取并解码音频
    while(av_read_frame(pFormatCtx, packet)>=0){
        if(packet->stream_index==audioStream){
            
            ret = avcodec_decode_audio4( pCodecCtx, pFrame,&got_picture, packet);
            if ( ret < 0 ) {
                printf("Error in decoding audio frame.\n");
                return -1;
            }
            if ( got_picture > 0 ){
                swr_convert(au_convert_ctx,&out_buffer, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)pFrame->data , pFrame->nb_samples);
                
                printf("index:%5d\t pts:%lld\t packet size:%d\n",index,packet->pts,packet->size);
                                index++;
                //
                //Set audio buffer (PCM data)
                audio_chunk = (Uint8 *) out_buffer;
                //Audio buffer length
                audio_len = out_buffer_size;
                audio_pos = audio_chunk;
                //Play
                SDL_PauseAudio(0);
                while(audio_len>0)//Wait until finish
                    SDL_Delay(1);
            }
        }
        av_free_packet(packet);
    }


    11. 音频填充方法
    void  fill_audio(void *udata,Uint8 *stream,int len){
    //SDL 2.0
    SDL_memset(stream, 0, len);
    if(audio_len==0)/*  Only  play  if  we  have  data  left  */
        return;
    len=(len>audio_len?audio_len:len);/*  Mix  as  much  data  as  possible  */
    
    SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME);
    audio_pos += len;
    audio_len -= len;
}




四、SDL2.0简单应用关键点
头文件包含
#import <SDL2/SDL.h>


1. SDL显示相关结构变量定义
SDL_Texture    *texture= NULL;
SDL_Renderer   *renderer = NULL;
    SDL_Window     *screen = NULL;


    2. SDL环境初始化
    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        exit(1);
    }
    注意:如果非main 方法中使用 则需要先调用 SDL_SetMainReady() 方法,避免使用SDL_Main方法作为主程序入口


    3. 创建显示设备窗口
    screen = SDL_CreateWindow("My Game Window",// 窗口标题
                              SDL_WINDOWPOS_UNDEFINED,// 窗口 x 坐标位置
                              SDL_WINDOWPOS_UNDEFINED,// 窗口 y 坐标位置
                              pCodecCtx->width,  pCodecCtx->height,// 窗口的宽高大小
                              SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL);// 窗口属性标记




    4. 创建显示状态结构
    renderer = SDL_CreateRenderer(screen, -1, 0); // 第二个参数 -1:第一个可用的驱动索引


    5. 创建显示文理结构
    texture = SDL_CreateTexture(renderer,// 当前渲染状态结构
    SDL_PIXELFORMAT_IYUV,
    SDL_TEXTUREACCESS_STREAMING,// 纹理格式
    pCodecCtx->width,// 表面纹理宽度
    pCodecCtx->height);// 表面纹理高度


    6. 显示过程方法
    SDL_UpdateTexture( texture, // 更新表面纹理数据
    &rect, // 更新纹理的矩形区域,可以为NULL,代表整个纹理面积区域
    pFrameYUV->data[0], // YUV数据地址
    pFrameYUV->linesize[0]);// 每一矩形行所占用的数据大小
        SDL_RenderClear( renderer ); // 清理旧的渲染状态,视频播放过程中也可以不用清理
        SDL_RenderCopy( renderer, texture, &rect, &rect );// 将纹理数据拷贝到驱动渲染结构中(个人理解为将显示数据拷贝到现存中)
        SDL_RenderPresent( renderer ); // 开始显示到屏幕上


    7. SDL结构内存数据释放
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(screen);




    总结:
    貌似利用这个自动化编译脚本产出的静态库中并没有 SDL_Image 图片处理或者加载的方法,这个SDL_Image工程源代码可以从GitHub下载,然后将libSDL2.a和头文件导入到工程中编译即可得到IOS版的SDL_Image图片处理能力,它支持从png、jpg等丰富的图片文件中创建SDL_Surface结构。不过需要将MobileCoreService和ImageIO两个库文件导入到工程中,否则会编译出错。
    静态库合并:
    lipo -create device.a simph.a -output union.a
    lipo -info **.a   # 可以查看该静态库支持的CPU架构体系


    另外,在 IOS 设备上只支持创建一个 SDL_Window, 以及 SDL_Renderer,因为IOS 应用都是单窗口应用,所以在移动设备上只能创建一个,这个限制在SDL2.0源代码文件中有判断。


    最后,如果想要在指定的UIView中显示画面内容需要作如下处理:
    #include <SDL2/SDL_syswm.h>
    ...
    SDL_SysWMinfo info;// SDL 系统窗口设备信息
        SDL_VERSION(&info.version); // 填充SDL版本信息


        // 获取SDL窗口设备信息
        if (SDL_FALSE != SDL_GetWindowWMInfo(window, &info)){
        // 获取 SDL 自己创建的 UIWindow 窗口对象
            UIWindow* uiWindow = (UIWindow*)info.info.uikit.window;
            // 取得窗口中的显示视图对象
            _innerRenderView = uiWindow.subviews[0];
            // 从父窗口中移除图像显示视图
            [_innerRenderView removeFromSuperview];


            // 将SDL图像显示视图添加到自己定义的 View中
            [dispView addSubview:_innerRenderView];
            
            // 修改显示视图的区域大小
            _innerRenderView.frame = CGRectMake(0, 0, dispView.frame.size.width, dispView.frame.size.height);
            // 这个很重要,需要隐藏SDL本身创建出来的 UIWindow ,否则该窗口一直在最顶层显示,下层控件并不能被操作。
            uiWindow.hidden = YES;
        }









0 0
原创粉丝点击