美文网首页
FFmpeg 解码本地 H.264

FFmpeg 解码本地 H.264

作者: i_have_an_Apple | 来源:发表于2016-12-15 17:28 被阅读468次

    最近学习 FFmpeg,自己写了一个小 Demo 解码一个 H.264 裸流数据
    FFmpeg 的编译导入等工作我们不再赘述,下面直接进入正题

    导入库文件

    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>
    

    声明一些我们需要用到的全局变量

    @interface LLQH264Decoder()<UIAlertViewDelegate>
    {
        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

          本文链接:https://www.haomeiwen.com/subject/aplgmttx.html