FFMpeg支持的视频格式比较多,故诸多播放器都是基于它进行二次开发的。我在之前的文章中有介绍如何编译一个在iOS下可用的FFMpeg,有需要的请看iOS 编译FFMpeg。
今天的文章主要讲怎么利用FFMpeg自己编写一个视频播放器,在开讲之前先简单了解下FFMpeg常用的类和方法。
AVFormatContext // 多媒体容器格式的封装、解封装工具
AVCodec // 解码相关类
AVFrame // 帧数据
Libswscale // 色彩转换、视频场景比例缩放。主要是将YUV数据转换为RGB个数数据
avformat_open_input // 打开视频文件,相对于拉流
avformat_find_stream_info // 主要是获取视频文件的容器信息
av_find_best_stream // 查找视频流、音频流、文字流等信息
avcodec_find_decoder // 查找解码器信息
avcodec_open2 // 打开解码器
av_read_frame // 读取每帧数据
avcodec_decode_video2 // 解码每帧视频方法
avcodec_decode_audio4 // 音频数据解码方法
avcodec_decode_subtitle2 // 字幕信息解码方法
sws_scale // 将解码后的YUV格式数据转换为RGB格式数据
视频播放的步骤
- 拿到视频url, avformat_open_input打开视频;
- avformat_find_stream_info打开视频文件容器,获取容器信息;
- av_find_best_stream获取容器中包含的视频流、音频流、字幕流;
- avcodec_find_decoder查找对应流的解码器;
- avcodec_open2 打开解码器;
- av_read_frame 读取流数据里面的帧数据;
- avcodec_decode_video2解码每帧视频数据;
- sws_scale 将YUV数据转换问RGB数据;
- 将RGB数据显示在界面上。
代码部分
初始化FFMpeg播放器
/**
传入视频文件路径初始化FFMpeg
@param moviePath 视频文件路径
@return (BOOL)YES- 初始化成功
NO -初始化失败
*/
-(BOOL)comitFFmpegWithPath:(NSString*)moviePath
{
// codec 用于各种类型声音、图像编解码
AVCodec *pCodec;
// 注册所有解码器
avcodec_register_all();
av_register_all();
avformat_network_init();
const char* filePath = [moviePath UTF8String];
// 打开视频文件, 拉流
int openVideo = avformat_open_input(&formatContext, filePath, NULL, NULL);
if (openVideo == 0)
{
NSLog(@"打开视频成功");
// 获取容器信息
int checkVideo = avformat_find_stream_info(formatContext, NULL);
if (checkVideo >= 0)
{
NSLog(@"打开容器包成功,数据完整");
// 查找音视频流、字幕流的stream_index, 找到流解码器
// AVMEDIA_TYPE_VIDEO 参数需要设置是视频还是音频
firstVideoStream = av_find_best_stream(formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0);
if (firstVideoStream >= 0)
{
NSLog(@"成功找到第一帧数据");
// 获取视频编解码的上下文指针
avStream = formatContext ->streams[firstVideoStream];
// 一些头文件信息
AVDictionary *metaData = avStream->metadata;
// 解码器
codecContext = avStream ->codec;
// 获取fps
// AVRational fps是分数来表示的 分子和分母都要大于0
if (avStream ->avg_frame_rate.den && avStream ->avg_frame_rate.num)
{
fps = av_q2d(avStream ->avg_frame_rate);
}
else
{
// 如果没有获取到,则默认为30
fps =30;
}
// 查找解码器
pCodec = avcodec_find_decoder(codecContext ->codec_id);
NSLog(@"%s",codecContext ->extradata);
if (pCodec == NULL)
{
NSLog(@"没有找到解码器");
return NO;
}
// 打开解码器
int codecOpen = avcodec_open2(codecContext, pCodec, NULL);
if (codecOpen <0)
{
NSLog(@"打开解码器失败");
return NO;
}
avFrame = av_frame_alloc();
frameImageWidth = codecContext ->width;
frameImageHeight = codecContext ->height;
playInitSuccess = YES;
return YES; // 初始化播放器成功
}
NSLog(@"没有找到第一帧视频");
return NO;
}
NSLog(@"数据流检查失败,数据不完整");
return NO;
}
NSLog(@"打开视频失败");
return NO;
}
解码每一帧数据
- (BOOL)decodeFrame
{
int decodeFinished = 0;
while (!decodeFinished && av_read_frame(formatContext, &avPacket) >=0 ) // 读取每一帧数据
{
NSLog(@"每帧数据%d",firstVideoStream);
if (avPacket.stream_index == firstVideoStream)
{
// 解码数据
// 解码一帧视频数据,存储到AVFrame中
avcodec_decode_video2(codecContext,avFrame, &decodeFinished, &avPacket);
}
}
if (decodeFinished == 0 )
{
[self releaseResources];
}
return decodeFinished !=0;
}
将YUV数据转换为RGB数据
/**
获取每帧图像
@return 每帧图片
*/
- (UIImage *)imageFromFrame
{
if ( !avFrame ->data[0])
{
return nil;
}
avpicture_free(&avPicture);
avpicture_alloc(&avPicture, AV_PIX_FMT_RGB24, frameImageWidth, frameImageHeight);
struct SwsContext *imageCovertContext = sws_getContext(avFrame->width, avFrame ->height, AV_PIX_FMT_YUV420P, frameImageWidth, frameImageHeight, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (imageCovertContext == nil)
{
return nil;
}
// YUV数据转化为RGB数据
sws_scale(imageCovertContext, avFrame->data, avFrame->linesize, 0, avFrame->height, avPicture.data, avPicture.linesize);
sws_freeContext(imageCovertContext);
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CFDataRef data = CFDataCreate(kCFAllocatorDefault,
avPicture.data[0],
avPicture.linesize[0] * frameImageHeight);
CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGImageRef cgImage = CGImageCreate(frameImageWidth,
frameImageHeight,
8,
24,
avPicture.linesize[0],
colorSpace,
bitmapInfo,
provider,
NULL,
NO,
kCGRenderingIntentDefault);
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CGColorSpaceRelease(colorSpace);
CGDataProviderRelease(provider);
CFRelease(data);
return image;
}
FFMpeg播放视频是获取视频一帧一帧的图片,利用UIImageView将其显示出来。
上传代码到GitHub。
网友评论