来源:http://www.aichengxu.com/view/37145

在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,这里播放的部分就略去了。

在iOS平台使用ffmpeg解码h264视频流的更多相关文章

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

    在iOS平台使用ffmpeg解码h264视频流,有需要的朋友可以参考下. 对于视频文件和rtsp之类的主流视频传输协议,ffmpeg提供avformat_open_input接口,直接将文件路径或UR ...

  2. FFmpeg解码H264及swscale缩放详解

    本文概要: 本文介绍著名开源音视频编解码库ffmpeg如何解码h264码流,比较详细阐述了其h264码流输入过程,解码原理,解码过程.同时,大部分应用环境下,以原始码流视频大小展示并不是最佳方式,因此 ...

  3. 【图像处理】FFmpeg解码H264及swscale缩放详解

      http://blog.csdn.net/gubenpeiyuan/article/details/19548019 主题 FFmpeg 本文概要: 本文介绍著名开源音视频编解码库ffmpeg如何 ...

  4. 多媒体开发(7):编译Android与iOS平台的FFmpeg

    编译FFmpeg,一个古老的话题,但小程还是介绍一遍,就当记录.之前介绍怎么给视频添加水印时,就已经提到FFmpeg的编译,并且在编译时指定了滤镜的功能. 但是,在手机盛行的时代,读者可能更需要的是能 ...

  5. 使用X264编码yuv格式的视频帧使用ffmpeg解码h264视频帧

    前面一篇博客介绍在centos上搭建点击打开链接ffmpeg及x264开发环境.以下就来问个样例: 1.利用x264库将YUV格式视频文件编码为h264格式视频文件 2.利用ffmpeh库将h264格 ...

  6. iOS平台在ffmpeg中使用librtmp

    转载请注明出处:http://www.cnblogs.com/fpzeng/p/3202344.html 系统版本:OS X 10.8 一.在iOS平台上交叉编译librtmp librtmp lin ...

  7. 实战FFmpeg--iOS平台使用FFmpeg将视频文件转换为YUV文件

    做播放器的开发这里面涉及的东西太多,我只能一步步往前走,慢慢深入.播放器播放视频采用的是渲染yuv文件.首先,要知道yuv文件是怎么转换得来的,其次,要知道怎么把视频文件保存为yuv文件.雷神的文章1 ...

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

    MJPEG格式和码流分析,MJPEG格式的一些简介 FFmpeg解码USB摄像头MJPEG输出:http://blog.csdn.net/light_in_dark/article/details/5 ...

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

    现在非常流行直播,相信很多人都跟我一样十分好奇这个技术是如何实现的,正好最近在做一个ffmpeg的项目,发现这个工具很容易就可以做直播,下面来给大家分享下技术要点: 首先你得编译出ffmpeg运行所需 ...

随机推荐

  1. Java的修饰、继承、接口、抽象类

     1.private 修饰属性或者方法,只能在本类中被访问,定义后需要加get()set()方法,这样提高数据的安全性 私有属性虽然不能直接访问,但是其对象 或者 子类对象可以通过公有方法进行设值和获 ...

  2. Random Access Iterator 徐州网络赛(树形dp)

    Random Access Iterator \[ Time Limit: 4000 ms \quad Memory Limit: 262144 kB \] 题意 给出伪代码,问按着伪代码在树上跑,能 ...

  3. vue-cli3.0启动项目,在局域网内其他电脑通过自己ip访问

    最近一直在使用vue-cli3.0做项目, package.json中配置后,自启动项目,也就没留意过小黑窗, "scripts": { "serve": &q ...

  4. 获取最新的中国IP的脚本,给ROS可以使用的脚本

    wget http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latestecho "/ip firewall address-li ...

  5. listings技巧

    1. \lstdefinestyle 参考 https://blog.csdn.net/ProgramChangesWorld/article/details/52142313 我们在使用listin ...

  6. [原创]Emmagee V2.4工具使用介绍

    [原创]Emmagee V2.4工具使用介绍 1 Emmagee 介绍 Emmagee 是网易杭州研究院 QA团队开发的一款简单易上手的Android性能监控App,主要用于监控单个App的CPU.内 ...

  7. spark 操作hive

    1.hive动态分区,只需进行以下设置 val spark = SparkSession.builder() .appName("hivetest") .master(" ...

  8. mapreduce 函数入门 三

    一.mapreduce多job串联 1.需求 一个稍复杂点的处理逻辑往往需要多个 mapreduce 程序串联处理,多 job 的串联可以借助 mapreduce 框架的 JobControl 实现 ...

  9. laravel 可以做什么

    laravel 可以做什么? Laravel是一套简洁.优雅的PHP Web开发框架(PHP Web Framework).它可以让你从面条一样杂乱的代码中解脱出来:它可以帮你构建一个完美的网络APP ...

  10. docker compose 编排

    Compose是Docker的服务编排工具,主要用来构建基于Docker的复杂应用,Compose 通过一个配置文件来管理多个Docker容器,非常适合组合使用多个容器进行开发的场景. 说明:Comp ...