FFmpeg 解码本地 H.264

最近学习 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;
        
    });
    
}

最后附上源码地址
这个解码还是比较简单的,需要静下心来研究一下

你可能感兴趣的:(FFmpeg 解码本地 H.264)