在iOS平台使用ffmpeg解码h264视频流(转)

在iOS平台使用ffmpeg解码h264视频流,有需要的朋友可以参考下。

对于视频文件和rtsp之类的主流视频传输协议,ffmpeg提供avformat_open_input接口,直接将文件路径或URL传入即可打开。读取视频数据、解码器初始参数设置等,都可以通过调用API来完成。

但是对于h264流,没有任何封装格式,也就无法使用libavformat。所以许多工作需要自己手工完成。

这里的h264流指AnnexB,也就是每个nal unit以起始码00 00 00 01 或 00 00 01开始的格式。关于h264码流格式,可以参考这篇文章

首先是手动设定AVCodec和AVCodecContext:

AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
avcodec_open2(codecCtx, codec, nil);

在AVCodecContext中会保存很多解码需要的信息,比如视频的长和宽,但是现在我们还不知道。

这些信息存储在h264流的SPS(序列参数集)和PPS(图像参数集)中。

对于每个nal unit,起始码后面第一个字节的后5位,代表这个nal unit的类型。7代表SPS,8代表PPS。一般在SPS和PPS后面的是IDR帧,无需前面帧的信息就可以解码,用5来代表。

检测nal unit类型的方法:

- (int)typeOfNalu:(NSData *)data
{
    char first = *(char *)[data bytes];
    return first & 0x1f;
}

264解码器在解码SPS和PPS的时候会提取出视频的信息,保存在AVCodecContext中。但是只把SPS和PPS传递进去是不行的,需要把后面的IDR帧一起传给解码器,才能够正确解码。

可以写一个简单的检测,如果接收到SPS,就把后面的PPS和IDR帧都接收过来,然后一起传给解码器。

初始化一个AVPacket和AVFrame,然后把SPS、PPS、IDR帧连在一起的数据块传给AVPacket的data指针,再进行解码。

我们假设包含SPS、PPS、IDR帧的数据块保存在videoData中,长度为len。

char *videoData;
int len;
AVFrame *frame = av_frame_alloc();
AVPacket packet;
av_new_packet(&packet, len);
memcpy(packet.data, videoData, len);
int ret, got_picture;
ret = avcodec_decode_video2(codecCtx, frame, &got_picture, &packet);
if (ret > 0){
    if(got_picture){
    //进行下一步的处理
    }
}

这样就可以顺利解码h264流了,解码出的数据保存在AVFrame中。

我写了一个Objective-C类用来执行接收视频流、解码、播放一系列步骤。

视频数据的接收采用socket直接接收,使用了开源项目CocoaAsyncSocket

就像项目名称中指明的,这是一个异步socket类。读写socket的动作会在一个单独的dispatch queue中执行,执行完毕后对应的delegate方法会自动调用,在其中进行进一步的处理。

读取h264流使用了GCDAsyncSocket 的 - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag 方法,也就是当读到和data中的字节一致的内容时就停止读取,并调用delegate方法。传入的data参数是 00 00 01 三个字节。这样每次读入的nalu开始是没有start code的,而最后面有下一个nalu的start code。因此每次读取之后都会把末尾的start code 暂存,然后把主体接到上一次暂存的start code之后,构成完整的nalu。

videoPlayer.h:

//videoPlayer.h
#import <Foundation/Foundation.h>

@interface videoPlayer : NSObject

- (void)startup;
- (void)shutdown;
@end

videoPlayer.m:

//videoPlayer.m

#import "videoPlayer.h"
#import "GCDAsyncSocket.h"

#import "libavcodec/avcodec.h"
#import "libswscale/swscale.h"

const int Header = 101;
const int Data = 102;

@interface videoPlayer () <GCDAsyncSocketDelegate>
{
    GCDAsyncSocket *socket;
    NSData *startcodeData;
    NSData *lastStartCode;

    //ffmpeg
    AVFrame *frame;
    AVPicture picture;
    AVCodec *codec;
    AVCodecContext *codecCtx;
    AVPacket packet;
    struct SwsContext *img_convert_ctx;

    NSMutableData *keyFrame;

    int outputWidth;
    int outputHeight;
}
@end

@implementation videoPlayer

- (id)init
{
    self = [super init];
    if (self) {
        avcodec_register_all();
        frame = av_frame_alloc();
        codec = avcodec_find_decoder(AV_CODEC_ID_H264);
        codecCtx = avcodec_alloc_context3(codec);
        int ret = avcodec_open2(codecCtx, codec, nil);
        if (ret != 0){
            NSLog(@"open codec failed :%d",ret);
        }

        socket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
        keyFrame = [[NSMutableData alloc]init];

        outputWidth = 320;
        outputHeight = 240;

        unsigned char startcode[] = {0,0,1};
        startcodeData = [NSData dataWithBytes:startcode length:3];
    }
    return self;
}

- (void)startup
{
    NSError *error = nil;
    [socket connectToHost:@"192.168.1.100"
                   onPort:9982
              withTimeout:-1
                    error:&error];
    NSLog(@"%@",error);
    if (!error) {
        [socket readDataToData:startcodeData withTimeout:-1 tag:0];
    }
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    [socket readDataToData:startcodeData withTimeout:-1 tag:Data];
    if(tag == Data){
        int type = [self typeOfNalu:data];
        if (type == 7 || type == 8 || type == 6 || type == 5) { //SPS PPS SEI IDR
            [keyFrame appendData:lastStartCode];
            [keyFrame appendBytes:[data bytes] length:[data length] - [self startCodeLenth:data]];
        }
        if (type == 5 || type == 1) {//IDR P frame
            if (type == 5) {
                int nalLen = (int)[keyFrame length];
                av_new_packet(&packet, nalLen);
                memcpy(packet.data, [keyFrame bytes], nalLen);
                keyFrame = [[NSMutableData alloc] init];//reset keyframe
            }else{
                NSMutableData *nalu = [[NSMutableData alloc]initWithData:lastStartCode];
                [nalu appendBytes:[data bytes] length:[data length] - [self startCodeLenth:data]];
                int nalLen = (int)[nalu length];
                av_new_packet(&packet, nalLen);
                memcpy(packet.data, [nalu bytes], nalLen);
            }

            int ret, got_picture;
            //NSLog(@"decode start");
            ret = avcodec_decode_video2(codecCtx, frame, &got_picture, &packet);
            //NSLog(@"decode finish");
            if (ret < 0) {
                NSLog(@"decode error");
                return;
            }
            if (!got_picture) {
                NSLog(@"didn‘t get picture");
                return;
            }
            static int sws_flags =  SWS_FAST_BILINEAR;
            //outputWidth = codecCtx->width;
            //outputHeight = codecCtx->height;
            if (!img_convert_ctx)
                img_convert_ctx = sws_getContext(codecCtx->width,
                                                 codecCtx->height,
                                                 codecCtx->pix_fmt,
                                                 outputWidth,
                                                 outputHeight,
                                                 PIX_FMT_YUV420P,
                                                 sws_flags, NULL, NULL, NULL);

            avpicture_alloc(&picture, PIX_FMT_YUV420P, outputWidth, outputHeight);
            ret = sws_scale(img_convert_ctx, (const uint8_t* const*)frame->data, frame->linesize, 0, frame->height, picture.data, picture.linesize);

            [self display];
            //NSLog(@"show frame finish");
            avpicture_free(&picture);
            av_free_packet(&packet);
        }
    }
    [self saveStartCode:data];
}

- (void)display
{

}

- (int)typeOfNalu:(NSData *)data
{
    char first = *(char *)[data bytes];
    return first & 0x1f;
}

- (int)startCodeLenth:(NSData *)data
{
    char temp = *((char *)[data bytes] + [data length] - 4);
    return temp == 0x00 ? 4 : 3;
}

- (void)saveStartCode:(NSData *)data
{
    int startCodeLen = [self startCodeLenth:data];
    NSRange startCodeRange = {[data length] - startCodeLen, startCodeLen};
    lastStartCode = [data subdataWithRange:startCodeRange];
}

- (void)shutdown
{
    if(socket)[socket disconnect];
}

- (void)dealloc
{
    // Free scaler
    if(img_convert_ctx)sws_freeContext(img_convert_ctx);

    // Free the YUV frame
    if(frame)av_frame_free(&frame);

    // Close the codec
    if (codecCtx) avcodec_close(codecCtx);
}

@end

在项目中播放解码出来的YUV视频使用了OPENGL,这里播放的部分就略去了。

时间: 2024-10-15 01:16:54

在iOS平台使用ffmpeg解码h264视频流(转)的相关文章

在iOS平台使用ffmpeg解码h264视频流

来源:http://www.aichengxu.com/view/37145 在iOS平台使用ffmpeg解码h264视频流,有需要的朋友可以参考下. 对于视频文件和rtsp之类的主流视频传输协议,ffmpeg提供avformat_open_input接口,直接将文件路径或URL传入即可打开.读取视频数据.解码器初始参数设置等,都可以通过调用API来完成. 但是对于h264流,没有任何封装格式,也就无法使用libavformat.所以许多工作需要自己手工完成. 这里的h264流指AnnexB,也

FFmpeg解码H264及swscale缩放详解

本文概要: 本文介绍著名开源音视频编解码库ffmpeg如何解码h264码流,比较详细阐述了其h264码流输入过程,解码原理,解码过程.同时,大部分应用环境下,以原始码流视频大小展示并不是最佳方式,因此,开发者不仅仅需要对视频流解码,并且需要缩放图像以展示于不同窗体下. 综上,本文除介绍ffmpeg解码h264,同时阐述如何使用swscale缩放视频流. 文章使用的开发环境Ubuntu12.04..交流邮箱:[email protected]. 转载请注明出处 CSDN--固本培元. ffmpeg

Android开发之《ffmpeg解码mjpeg视频流》

FFmpeg解码USB摄像头MJPEG输出:http://blog.csdn.net/light_in_dark/article/details/56276799?locationNum=14&fps=1

iOS平台基于ffmpeg的视频直播技术揭秘

现在非常流行直播,相信很多人都跟我一样十分好奇这个技术是如何实现的,正好最近在做一个ffmpeg的项目,发现这个工具很容易就可以做直播,下面来给大家分享下技术要点: 首先你得编译出ffmpeg运行所需的静态库,这个百度一下有很多内容,这里我就不多说了,建议可以用Github上的一个开源脚本来编译,简单粗暴有效率. 地址:GitHub - kewlbear/FFmpeg-iOS-build-script: Shell scripts to build FFmpeg for iOS and tvOS

(转)FFMPEG解码H264拼帧简解

http://blog.csdn.net/ikevin/article/details/7649095 H264的I帧通常 0x00 0x00 0x00 0x01 0x67 开始,到下一个帧头开始之前是完整一帧.可放入FFMPEG的AVPacket中处理无论是文件流还是网络流,思路是将接收到的数据放入缓冲区,同时开启一个待拼帧的缓冲区1024*1024大小(我的是高清1920*1080,足够) 一.寻I帧头 //判断H264的I帧数据.返回I帧在本缓冲位置,或-1未找到int _find_hea

实战FFmpeg + OpenGLES--iOS平台上视频解码和播放

一个星期的努力终于搞定了视频的播放,利用FFmpeg解码视频,将解码的数据通过OpenGLES渲染播放.搞清楚了自己想知道的和完成了自己的学习计划,有点小兴奋.明天就是“五一”,放假三天,更开心啦. 本文实现视频文件的播放是在自己之前写的文章实战FFmpeg--iOS平台使用FFmpeg将视频文件转换为YUV文件 . 实战OpenGLES--iOS平台使用OpenGLES渲染YUV图片 的基础上改进合成来完成的.不多种解释,直接上代码,清晰明了. NSString *path = [[NSBun

FFMPEG实现H264的解码(从源代码角度)

农历2014年底了,将前段时间工作中研究的FFMPEG解码H264流程在此做一下整理,也算作年终技术总结了! H264解码原理: H264的原理参考另一篇博文 http://blog.csdn.net/rootusers/article/details/43563133 H264分为NAL(网络抽象层)和VCL(视频编码层) 解码器的总框架: 解码器的流程为:将NAL数据位流输入到H264的解码器中,熵解码模块解码后输出量化系数X:系数经过反量化和反变换得到残差数据R:解码器使用从码流中解码的头

[iOS]FFmpeg框架在iOS平台上的编译和使用

使用环境 Mac OS Yosemite 10.10.5 开发工具 Xcode 7.0 Terminal 需要的文件链接 gas-preprocessor yasm FFmpeg-iOS-build-script ffmpeg-2.8 kxmovie 编译适用于iOS平台的FFmpeg静态库 打开终端Terminal进入下载后的gas-preprocessor文件夹 将文件夹内的gas-preprocessor.pl文件拷贝到/usr/sbin/目录下 修改/usr/sbin/gas-prepr

ffmpeg解码RTSP/TCP视频流H.264(QT界面显示视频画面)

我用的ffmpeg版本为 ffmpeg-2.1.8.tar.bz2 版本低了恐怕有些头文件和API找不到. 在Linux下解压后编译,Linux下编译很简单,我这里生成的动态库: ./configure –enable-shared make 就能找到各个so动态库文件. 移动位置后,记得手动链接 一下: ln -s libavcodec.so.55 libavcodec.so ln -s libavdevice.so.55 libavdevice.so ln -s libavfilter.so