最近学习 FFmpeg,自己写了一个小 Demo 解码一个 H.264 裸流数据
FFmpeg 的编译导入等工作我们不再赘述,下面直接进入正题
导入库文件
#include
#include
#include
声明一些我们需要用到的全局变量
@interface LLQH264Decoder()
{
AVFormatContext *pFormatCtx; //解码上下文,贯穿整个解码过程
int i,videoIndex;
AVCodecContext *pCodecCtx; //解码器上下文,存储解码器、解码信息等
AVCodec *pCodec; //解码器
AVFrame *pFrame, *pFrameYUV; //存储每一帧的原始数据
uint8_t *out_Buffer;
AVPacket *packet; //存储每一帧的解码后数据
int ret,got_picture;
struct SwsContext *img_convert_ctx;
int frame_cnt;
}
准备工作已经做完了,下面开始重点
解码部分
我们通过传入一个 H.264 文件的路径,来读取这个文件
- (void)setupFFMPEGwithPath:(NSString *)path{
//注册编解码器
av_register_all();
//
avformat_network_init();
//初始化 贯穿整个解码的解码上下文
pFormatCtx = avformat_alloc_context();
//打开文件 返回0表示成功,所有数据存储在formatCtx中
if (avformat_open_input(&pFormatCtx, path.UTF8String, NULL, NULL) != 0) {
[self showAlerViewTitle:@"不能打开流文件"];
return;
}
//读取数据包获取流媒体文件的信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
[self showAlerViewTitle:@"不能读取到流信息"];
return;
}
videoIndex = -1;
//查找视频流
//nb_streams视音频流的个数
//streams视音频流
for (i = 0; i < pFormatCtx->nb_streams; i ++) {
//直至查找到视频流
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
NSLog(@"videoIndex==%d",videoIndex); //视频流的下标
break;
}
if (videoIndex == -1) {
[self showAlerViewTitle:@"没有视频流"];
return;
}
}
//取出查找到的视频流的解码器信息
pCodecCtx = pFormatCtx->streams[videoIndex]->codec;
//初始化解码器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
[self showAlerViewTitle:@"找不到解码器"];
return;
}
//打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
[self showAlerViewTitle:@"不能打开解码器"];
return;
}
//初始化frame,packet
//AVPacket里面的是H.264码流数据
//AVFrame里面装的是YUV数据。YUV是经过decoder解码AVPacket的数据
pFrame = av_frame_alloc();
packet = (AVPacket *)malloc(sizeof(AVPacket));
//打印一大堆时间、比特率、流、容器、编解码器和时间等
av_dump_format(pFormatCtx, 0, path.UTF8String, 0);
//为解码为image做准备
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
frame_cnt = 0;
//开辟线程操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//从formatCtx中读取,一帧一帧的读取,循环一次,就读取一帧
while (av_read_frame(pFormatCtx, packet) >= 0) {
NSLog(@"packet->data==%d",packet->size);
if (packet->stream_index == videoIndex) {
//根据获取到的packet生成pFrame(AVFrame)实际上就是解码
//如果没有需要解码的帧则got_picture就会为0
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0) {
[self showAlerViewTitle:@"解码错误"];
return;
}
if (got_picture) {
//这里是播放部分,有两种播放方式,用 OpenGL 播放
//OpenGL GPU渲染
[self makeYUVframe];
//imageView播放,我们将解码获得的 YUV 数据解码为 image,然后交给 imageview
// [self makeImage];
}
}
av_free_packet(packet);
}
//最后释放我们用到的结构体
sws_freeContext(img_convert_ctx);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
[[NSNotificationCenter defaultCenter] postNotificationName:decodeDidFinishNotification object:nil];
});
}
播放部分
OpenGL播放
//YUV转RGB
- (void)makeYUVframe{
unsigned int lumaLength = (pCodecCtx->height)*(MIN(pFrame->linesize[0], pCodecCtx->width));
unsigned int chromBLength = ((pCodecCtx->height)/2)*(MIN(pFrame->linesize[1], (pCodecCtx->width)/2));
unsigned int chromRLength = ((pCodecCtx->height)/2)*(MIN(pFrame->linesize[1], (pCodecCtx->width)/2));
//初始化
H264YUV_Frame yuvFrame;
//此函数的意思是将 sizeof(H264YUV_Frame) 大小的 0 数据,拷贝到这个地址&yuvFrame 实际上就是初始化
memset(&yuvFrame, 0, sizeof(H264YUV_Frame));
yuvFrame.luma.length = lumaLength;
yuvFrame.chromaB.length = chromBLength;
yuvFrame.chromaR.length = chromRLength;
yuvFrame.luma.dataBuffer = (unsigned char*)malloc(lumaLength);
yuvFrame.chromaB.dataBuffer = (unsigned char*)malloc(chromBLength);
yuvFrame.chromaR.dataBuffer = (unsigned char*)malloc(chromRLength);
//转RGB
copyDecodedFrame(pFrame->data[0], yuvFrame.luma.dataBuffer, pFrame->linesize[0], pCodecCtx->width, pCodecCtx->height);
copyDecodedFrame(pFrame->data[1], yuvFrame.chromaB.dataBuffer, pFrame->linesize[1], pCodecCtx->width/2, pCodecCtx->height/2);
copyDecodedFrame(pFrame->data[2], yuvFrame.chromaR.dataBuffer, pFrame->linesize[2], pCodecCtx->width/2, pCodecCtx->height/2);
yuvFrame.width = pCodecCtx->width;
yuvFrame.height = pCodecCtx->height;
//在主线程中把获得到的 RGB 更新出去,这里通过代理
dispatch_sync(dispatch_get_main_queue(), ^{
if([self.delegate respondsToSelector:@selector(updateYUVFrameOnMainThread:)]){
[self.delegate updateYUVFrameOnMainThread:(H264YUV_Frame *)&yuvFrame];
}
});
//最后释放
free(yuvFrame.luma.dataBuffer);
free(yuvFrame.chromaB.dataBuffer);
free(yuvFrame.chromaR.dataBuffer);
}
//转RGB算法
void copyDecodedFrame(unsigned char *src, unsigned char *dist,int linesize, int width, int height)
{
width = MIN(linesize, width);
for (NSUInteger i = 0; i < height; ++i) {
memcpy(dist, src, width);
dist += width;
src += linesize;
}
}
这里的 OpenGL 播放我用的是前辈写的 OpenGL 播放器,只需要传入RGB 数据就可以播放了,下面是代理方法的实现
#pragma mark ------ LLQH264DecoderDelegate
- (void)updateYUVFrameOnMainThread:(H264YUV_Frame *)yuvFrame{
//只需要调用这个方法,就可以播放了
[_openGLFrameView render:yuvFrame];
}
imageView播放
同样是利用代理方法将获得的图片更新出去
//转为image
- (void)makeImage{
//给picture分配空间
AVPicture pictureL = [self AllocAVPicture];
int pictRet = sws_scale (img_convert_ctx,(const uint8_t * const *)pFrame->data, pFrame->linesize,
0, pCodecCtx->height,
pictureL.data, pictureL.linesize);
if (pictRet > 0) {
UIImage * image = [self imageFromAVPicture:pictureL width:pCodecCtx->width height:pCodecCtx->height];
[NSThread sleepForTimeInterval:1.0/80.0];
if ([self.delegate respondsToSelector:@selector(updateImageOnMainTread:)]) {
[self.delegate updateImageOnMainTread:image];
}
}
//释放AVPicture
avpicture_free(&pictureL);
}
这边是一些转为image用到的算法
-(AVPicture)AllocAVPicture
{
//创建AVPicture
AVPicture pictureL;
sws_freeContext(img_convert_ctx);
avpicture_alloc(&pictureL, PIX_FMT_RGB24,pCodecCtx->width,pCodecCtx->height);
static int sws_flags = SWS_FAST_BILINEAR;
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_RGB24,
sws_flags, NULL, NULL, NULL);
return pictureL;
}
/**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;
}
代理方法的实现,只需要将图片交给 imageView
#pragma mark ------ LLQH264DecoderDelegate
- (void)updateImageOnMainTread:(UIImage *)image{
dispatch_sync(dispatch_get_main_queue(), ^{
_imageView.image = image;
});
}
最后附上源码地址
这个解码还是比较简单的,需要静下心来研究一下