您的位置:首页 > 其它

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

2016-03-13 14:52 162 查看
# 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;

        }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ffmpeg sdl