iFrameExtractor地址:https://github.com/lajos/iFrameExtractor
ffmpeg的簡介
FFmpeg是一套可以用來記錄、轉換數字音頻、視頻,並能將其轉化為流的開源計算機程序。
"FFmpeg"這個單詞中的"FF"指的是"Fast Forward"。
ffmpeg支持的格式
ASF
AVI
BFI
FLV
GXF, General eXchange Format, SMPTE 360M
IFF
RL2
ISO base media file format(包括QuickTime, 3GP和MP4)
Matroska(包括WebM)
Maxis XA
MPEG program stream
MPEG transport stream(including AVCHD)
MXF, Material eXchange Format, SMPTE 377M
MSN Webcam stream
Ogg
OMA
TXD
WTV
ffmpeg支持的協議
IETF標准:TCP, UDP, Gopher, HTTP, RTP, RTSP和SDP
蘋果公司的相關標准:HTTP Live Streaming
RealMedia的相關標准:RealMedia RTSP/RDT
Adobe的相關標准:RTMP, RTMPT(由librtmp實現),RTMPE(由librtmp實現),RTMPTE(由librtmp)和RTMPS(由librtmp實現)
微軟的相關標准:MMS在TCP上和MMS在HTTP上
iFrameExtractor的使用
初始化
self.video = [[VideoFrameExtractor alloc] initWithVideo:[Utilities bundlePath:@"sophie.mov"]]; video.outputWidth = 426; video.outputHeight = 320;
播放
[video seekTime:0.0]; [NSTimer scheduledTimerWithTimeInterval:1.0/30 target:self selector:@selector(displayNextFrame:) userInfo:nil repeats:YES]; -(void)displayNextFrame:(NSTimer *)timer { if (![video stepFrame]) { return; } imageView.image = video.currentImage; }
VideoFrameExtractor類解析
initWithVideo:(NSString *)moviePath方法
VideoFrameExtractor的初始化,主要是配置三個全局的結構體變量。
AVFormatContext類型的pFormatCtx,AVFormatContext主要存儲視音頻封裝格式中包含的信息;AVInputFormat存儲輸入視音頻使用的封裝格式。每種視音頻封裝格式都對應一個AVInputFormat 結構。
AVCodecContext類型的pCodecCtx ,每個AVStream存儲一個視頻/音頻流的相關數據;每個AVStream對應一個AVCodecContext,存儲該視頻/音頻流使用解碼方式的相關數據;每個AVCodecContext中對應一個AVCodec,包含該視頻/音頻對應的解碼器。每種解碼器都對應一個AVCodec結構。
AVFrame類型的pFrame,視頻的話,每個結構一般是存一幀,音頻可能有好幾幀。解碼前數據是AVPacket,解碼後數據是AVFrame。
FMPEG中結構體很多。最關鍵的結構體他們之間的對應關系如下所示:
圖片來自:FFMPEG中最關鍵的結構體之間的關系
下面就是初始化的代碼
-(id)initWithVideo:(NSString *)moviePath { if (!(self=[super init])) return nil; AVCodec *pCodec; // Register all formats and codecs avcodec_register_all(); av_register_all(); // Open video file if(avformat_open_input(&pFormatCtx, [moviePath cStringUsingEncoding:NSASCIIStringEncoding], NULL, NULL) != 0) { av_log(NULL, AV_LOG_ERROR, "Couldn't open file\n"); goto initError; } // Retrieve stream information if(avformat_find_stream_info(pFormatCtx,NULL) < 0) { av_log(NULL, AV_LOG_ERROR, "Couldn't find stream information\n"); goto initError; } // Find the first video stream if ((videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0)) < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot find a video stream in the input file\n"); goto initError; } // Get a pointer to the codec context for the video stream pCodecCtx = pFormatCtx->streams[videoStream]->codec; // Find the decoder for the video stream pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec == NULL) { av_log(NULL, AV_LOG_ERROR, "Unsupported codec!\n"); goto initError; } // Open codec if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot open video decoder\n"); goto initError; } // Allocate video frame pFrame = avcodec_alloc_frame(); outputWidth = pCodecCtx->width; self.outputHeight = pCodecCtx->height; return self; initError: [self release]; return nil; }
sourceWidth和sourceHeight方法
獲取屏幕的寬和高
-(int)sourceWidth { return pCodecCtx->width; } -(int)sourceHeight { return pCodecCtx->height; }
setupScaler方法
設置視頻播放視圖的尺寸
-(void)setupScaler { // Release old picture and scaler avpicture_free(&picture); sws_freeContext(img_convert_ctx); // Allocate RGB picture avpicture_alloc(&picture, PIX_FMT_RGB24, outputWidth, outputHeight); // Setup scaler static int sws_flags = SWS_FAST_BILINEAR; img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, outputWidth, outputHeight, PIX_FMT_RGB24, sws_flags, NULL, NULL, NULL); }
duration方法
獲取音視頻文件的總時間
-(double)duration { return (double)pFormatCtx->duration / AV_TIME_BASE; }
currentTime方法
顯示音視頻當前播放的時間
-(double)currentTime { AVRational timeBase = pFormatCtx->streams[videoStream]->time_base; return packet.pts * (double)timeBase.num / timeBase.den; }
seekTime:(double)seconds方法
直接跳到音視頻的第seconds秒進行播放,默認從第0.0秒開始
-(void)seekTime:(double)seconds { AVRational timeBase = pFormatCtx->streams[videoStream]->time_base; int64_t targetFrame = (int64_t)((double)timeBase.den / timeBase.num * seconds); avformat_seek_file(pFormatCtx, videoStream, targetFrame, targetFrame, targetFrame, AVSEEK_FLAG_FRAME); avcodec_flush_buffers(pCodecCtx); }
stepFrame方法
解碼視頻得到幀
-(BOOL)stepFrame { // AVPacket packet; int frameFinished=0; while(!frameFinished && av_read_frame(pFormatCtx, &packet)>=0) { // Is this a packet from the video stream? if(packet.stream_index==videoStream) { // Decode video frame avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); } } return frameFinished!=0; }
currentImage方法
獲取當前的UIImage對象,以呈現當前播放的畫面
-(UIImage *)currentImage { if (!pFrame->data[0]) return nil; [self convertFrameToRGB]; return [self imageFromAVPicture:picture width:outputWidth height:outputHeight]; }
convertFrameToRGB
轉換音視頻幀到RGB
-(void)convertFrameToRGB { sws_scale (img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picture.data, picture.linesize); }
(UIImage *)imageFromAVPicture:(AVPicture)pict width:(int)width height:(int)height方法
把AVPicture轉換成UIImage把音視頻畫面顯示出來
-(UIImage *)imageFromAVPicture:(AVPicture)pict width:(int)width height:(int)height { CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, pict.data[0], pict.linesize[0]*height,kCFAllocatorNull); CGDataProviderRef provider = CGDataProviderCreateWithCFData(data); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGImageRef cgImage = CGImageCreate(width, height, 8, 24, pict.linesize[0], colorSpace, bitmapInfo, provider, NULL, NO, kCGRenderingIntentDefault); CGColorSpaceRelease(colorSpace); UIImage *image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); CGDataProviderRelease(provider); CFRelease(data); return image; }
Reference
ElevenPlayer: 這是我用ffmpeg寫的iOS萬能播放器。
iOS配置FFmpeg框架
FFmpeg-wikipedia
Vitamio測試網絡視頻地址
FFMPEG結構體分析-系列文章:包括AVFrame、AVFormatContext、AVCodecContext、AVIOContext、AVCodec、AVStream、AVPacket
FFmpeg開發和使用有關的文章的匯總
ffmpeg 官網
FFmpeg GitHub source code
作者 :coderyi