# 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;
}
一、源代码下载和安装
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;
}