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。