音视频编解码:视频编解码基础1-FFmpeg结构与API摘要
音视频编解码:视频编解码基础2-FFmpeg解码实践
音视频编解码:视频编解码基础篇3-FFmpeg转码实践
音视频编解码:视频编解码基础篇4-FFmpeg解码播放
FFmpeg转码流程
1.注册所有编解码器
2.根据URL打开媒体文件
3.检索视频流信息
4.根据视频流编解码上下文获取编码格式
5.根据编码格式打开编码器
6.读取音视频数据 packet
7.获得两路流视频流解码成 YUV
8.YUV->CVPixelBufferRef->CMSampleBufferRef
9.通过AVAssetWriter和预定的封装格式转储封装预定格式视频.
AVFormatContext *_inFormatCtx;
SwsContext *_videoSwsContext;
AVCodecContext *_videoDecodecCtx;
AVCodecParameters *_videoDecodecParameters;
AVSampleFormat _destSampleFmt;
void *_swrBuffer;
NSUInteger _swrBufferSize;
AVFrame *_destVideoFrame;
AVFrame *_videoFrame;
AVRational _videoFPSTimebase;
AVRational _audioFPSTimebase;
NSLock * _lock;
BOOL _closeStream;
BOOL _errorOccur;
BOOL _isTransCoding;
int _destWidth;
int _destHeight;
unsigned char* _yuv420Buffer;
- (BOOL)openInput:(NSString *)urlString{
//组册组件
av_register_all();
int result = -1;
frameNum = 0;
do {
_inFormatCtx = avformat_alloc_context();
if (!_inFormatCtx) break;
//打开封装格式->打开文件
result = avformat_open_input(&_inFormatCtx, [urlString cStringUsingEncoding:NSUTF8StringEncoding], NULL, NULL);
if (result < 0) break;
//查找流信息
result = avformat_find_stream_info(_inFormatCtx, NULL);
if (result < 0) break;
//检索对应的音视频流
for (int i = 0; i < _inFormatCtx->nb_streams; i++) {
AVStream *stream = _inFormatCtx->streams[i];
AVCodecParameters* codecPara = stream->codecpar;;
if (AVMEDIA_TYPE_VIDEO == codecPara->codec_type) {
result = [self openVideoStream:stream];
if (result < 0) break;
} else if (AVMEDIA_TYPE_AUDIO == codecPara->codec_type){
暂不对音频做处理
// result = [self openAudioStream:stream];
//if (result < 0) break;
}
}
AVCodec *dec;
// 选择,查找视频流
result = av_find_best_stream(_inFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);
if (result < 0) {
printf( "找不到视频输入信息\n");
return result;
}
video_stream_index = result;
pCodecCtx = _inFormatCtx->streams[video_stream_index]->codec;
if (result < 0) break;
// 打印视频流信息
av_dump_format(_inFormatCtx, 0, [inputURL.lastPathComponent cStringUsingEncoding: NSUTF8StringEncoding], false);
} while (0);
return result >= 0;
}
-(int)openVideoStream:(AVStream*)videoStream{
AVCodecParameters* codecParam = videoStream->codecpar;
AVCodecContext* content = NULL;
int result = -1;
do {
//查找解码器
AVCodec* codec = avcodec_find_decoder(codecParam->codec_id);
if (!codec) {
printf("\n 没有找到解码器:%s \n", avcodec_get_name(codecParam->codec_id));
break;
}
// 配置解码器
content = avcodec_alloc_context3(codec);
if (!content) break;
result = avcodec_parameters_to_context(content, codecParam);
if (result < 0) break;
// 打开解码器
result = avcodec_open2(content, codec, NULL);
if (result < 0) break;
_videoFrame = av_frame_alloc();
_videoDecodecCtx = content;
_videoDecodecParameters = codecParam;
_destWidth = MIN(_destWidth, codecParam->width);
_destHeight = _destWidth * codecParam->height / codecParam->width / 4 * 4;
if (videoStream->avg_frame_rate.den && videoStream->avg_frame_rate.num) {
_videoFPSTimebase = videoStream->avg_frame_rate;
} else if (videoStream->r_frame_rate.den && videoStream->r_frame_rate.num){
_videoFPSTimebase = videoStream->r_frame_rate;
} else {
_videoFPSTimebase = av_make_q(25, 1);
}
if (_destWidth != _videoDecodecCtx->width || _destHeight != _videoDecodecCtx->height || AV_PIX_FMT_YUV420P != _videoDecodecCtx->pix_fmt) {
/*
sws_getContext()
sws_scale()
sws_freeContext()
设置sws_getContext配置获取SwsContext结构体,包括视频制式YUV420P,大小,格式
*/
[_lock lock];
[self closeSwsCtx];
_destVideoFrame = alloc_image(AV_PIX_FMT_YUV420P, _destWidth, _destHeight);
_videoSwsContext = sws_getContext(_videoDecodecParameters->width, _videoDecodecParameters->height, (AVPixelFormat)_videoDecodecParameters->format,
_destWidth, _destHeight, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
[_lock unlock];
}
} while (0);
if (result < 0) {
if (content) {
avcodec_free_context(&content);
content = NULL;
}
}
return result;
}
/*alloc_image()此自定义函数的功能是按照指定的宽、高、像素格式来分析图像内存。
核心函数为
int av_image_alloc(uint8_t *pointers[4], int linesizes[4],int w, int h, enum AVPixelFormat pix_fmt, int align);
int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align);
在此配置都可,相关选择和异同将在后续API对比中详细说明
*/
AVFrame* alloc_image(AVPixelFormat pix_fmt, int width, int height)
{
AVFrame *frame ;
uint8_t *frame_buf;
int size;
frame = av_frame_alloc();
frame->width = width;
frame->height = height;
if (!frame) return NULL;
size = av_image_get_buffer_size(pix_fmt, width, height, 1);
frame_buf = (uint8_t *)av_malloc(size);
if (!frame_buf){
av_frame_free(&frame);
return NULL;
}
av_image_fill_arrays(frame->data, frame->linesize, frame_buf, pix_fmt, width, height, 1);
return frame;
}
//读取视频帧线程
-(void)startTranscoding{
[NSThread detachNewThreadSelector:@selector(readFramesThread) toTarget:self withObject:nil];
}
//子线程读取视频帧
-(void)readFramesThread{
@autoreleasepool {
// 数据包初始化
AVPacket pkt;
av_init_packet(&pkt);
CGFloat completePercent = 0;
CGFloat prePercent = 0;
NSLog (@"开始转码进度:duration = %f ", [self duration]);
while (!_closeStream && !_errorOccur) {
[_lock lock];
if (_closeStream) {
[_lock unlock];
break;
}
int ret = av_read_frame(_inFormatCtx, &pkt);
if (ret < 0) {
if (AVERROR_EOF == ret) {
completePercent = 1.0;
}
NSLog@("读取视频错误error-->, %s", av_err2str(ret))
[_lock unlock];
break;
}
int streamIndex = pkt.stream_index;
AVStream* in_stream = _inFormatCtx->streams[streamIndex];
if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type) {
completePercent = [self getCompletePercent:&pkt];
if (completePercent >= prePercent + 0.01) {//避免频繁的回调
prePercent = completePercent;
//回调转码进度
}
[self decodeVideo:&pkt];
} else if(AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type){
//[self decodeAudio:&pkt];
} else {
/* AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as
AVMEDIA_TYPE_DATA
AVMEDIA_TYPE_VIDEO,
AVMEDIA_TYPE_AUDIO,
AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuous
AVMEDIA_TYPE_SUBTITLE,
AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse
AVMEDIA_TYPE_NB*/
}
av_packet_unref(&pkt);
[_lock unlock];
[NSThread sleepForTimeInterval:0.005];//避免此线程cup占用过高
}
if (!_closeStream) {
//如果 _closeStream = true, 说明是外部主动close的,转码结束.
[self.assetWriter stopRecord:^(BOOL isSucced) {
if (isSucced && completePercent > .90) {
//文件转码结束,回调主程转储存文件savePath
} else {
//转储存文件出错
}
_isTransCoding = false;
}];
} else if (_errorOccur) {
//转码结束出错
}else{
//文件转码结束,回调主程转储存文件savePath
}
NSLog(@"文件转码结束,completePercent = %f", completePercent);
}
}
待续...