最近开始将陆续写写iOS 流媒体相关的文章,其中包括简单的实现方式和FFmpeg,主要写写这么做了这么多次监控方面的心得吧,也都是自己的理解,如果有误的地方还望见谅并指出。
Jasper(ZR)原创文章,转载请注明出处。
先大概说一下,iOS播放IPCamera,也是就是网络摄像头,多用于做监控功能,实现的方式有大致有几种1.解码MJ流;2.FFmpeg解码流媒体;3.使用Kxmovie。这里面1最简单,但是只能看见图像没有声音,下面会解释为什么没有声音。2是使用开源FFmpeg,难度不小,不适合新手接触,因为想实现功能简单,但是想做的好很难,博主做过多次FFmpeg,每次都觉得仍有不足的地方,而且加上声音的时候想做音视频同步解码也是个不小的难点,总之还是那句话,做出来容易,想做好不容易。3是使用现成的Kxmovie,也是一个使用FFmpeg的开源播放器项目,完成度很高,可以直接引用进项目里面~~~大体就先说这些,方法还有其他的,比如VLC等等,Kxmovie和VLC都是把FFmpeg用到如火纯情的地步(个人感觉)。
本文先写MJ流,一起看一下百度百科摘抄:
M-JPEG是一种基于静态图像压缩技术JPEG发展起来的动态图像压缩技术,可以生成序列化的运动图像。其主要特点是基本不考虑视频流中不同帧之间的变化,只单独对某一帧进行压缩,其压缩倍数为20~80倍,适合静态画面的压缩,分辨率可从352×288到704×576。
这里可以得知MJ是一种静态图片压缩成的视频流,所以一般不包含声音(除非复合的音视频流),每一帧都可以当成一张JPEG图片去解,JPEG的标志位是0xFF 0xD8开始,0xFF 0xD9结束,由此分析此种简单的解码办法其原理就是按照标志去解析每帧JPEG图像再连续播放出来。
这样分析一来解码就很容易了,用网络请求获取到连续的MJ流之后进行JPEG解码之后输出,显示可以有多种方式,简单点的就是imageView贴图,虽然方法很low,但是真的很简单,但是效果和效率一般,需求不高还可以~~再者就是SDL,这块需要了解的知识也很多,常见的是FFmpeg + SDL,这里先不讲这些~~~因为SDL博主掌握的也不好(*^__^*) 。。。
我的做法是自定义一个CameraView,继承自imageView,内部封装了网络请求,和一些常用的方法(暂停、播放、截图等),解码一帧图片后就可以直接给自身贴图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// // CameraView.h // Wall.E-Btn // // Created by Jasper on 14-6-30. // Copyright (c) 2014年 zhujia18. All rights reserved. // #import <UIKit/UIKit.h> @protocol CameraViewDelegate <NSObject> @optional - (void)cameraLoading; - (void)cameraDidLoad; - (void)cameraLoadFaild; @end @interface CameraView : UIImageView { NSURL *videoURL; NSURLConnection *connection; NSData *endMarkerData; NSMutableData *receivedData; } @property(nonatomic, assign) id <CameraViewDelegate> delegate; @property(nonatomic, retain) NSURL *videoURL; - (void)play; - (void)pause; - (void)stopPlay; - (void)cleanupConnection; - (UIImage *)takePhoto; @end |
这里面其他的方法就不讲了,只说说怎么解每一帧图片,首先知道每帧JPEG的开始和结束是D8和D9,那么我们先define END_MARKER_BYTES {0xFF, 0xD9},然后网络接收到的是持续的data,用NSRange判断此次接收到的NSData里面是否包含结束标志,这里默认每个Data都是每帧的开始,如果检测到结束标志,再把起始到结束这段Data用Range获取出来转成img,并贴图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [receivedData appendData:data]; NSRange endRange = [receivedData rangeOfData:endMarkerData options:0 range:NSMakeRange(0, receivedData.length)]; long long endLocation = endRange.location + endRange.length; if (receivedData.length >= endLocation) { NSRange imageRange = NSMakeRange(0, endLocation); NSData *imageData = [receivedData subdataWithRange:imageRange]; UIImage *receivedImage = [UIImage imageWithData:imageData]; if (receivedImage) { self.image = receivedImage; NSLog(@"解码图片"); } [receivedData release]; /* NSLog(@"imageRange: %@",NSStringFromRange(imageRange)); NSRange tempRange = NSMakeRange(endLocation + 1, receivedData.length - endLocation); NSLog(@"tempRange: %@",NSStringFromRange(tempRange)); NSData *tempData = [[NSData alloc] initWithData:[receivedData subdataWithRange:tempRange]]; [receivedData release]; receivedData = [[NSMutableData alloc] init]; [receivedData appendData:tempData]; [tempData release];*/ } } |
这里面有两套解析方式,一套是刚才上面描述的,还注释了一段,注释了一段是因为担心上面提取出来的Data作为一帧,剩下的Data可以看见已经释放了,然后又是从头开始的解下一帧,担心这样如果每次截取到标识符之后后面还有下一帧的部分Data,这样就会造成丢帧。所以当时写了一个交换,就是每次获取完一帧data,把剩余的data留下来,之后跟下一次的先拼接再解析,这样理论上可以防止丢帧,后来看log的时候,发现我身边的摄像头每次都是完整的一个Data正好一帧,我就把这套方案给注释掉了。
总结,对于流媒体,我可以说熟悉,因为早在2011年的时候就在大学做流媒体闭路电视转播,很早就接触流媒体的概念,也可以说不熟悉,因为至今我对它了解的深度依然不够,很多概念也是不全明白,现在了解的这些是2013年做智能家居的项目,做到FFmpeg流媒体监控的时候,翻看国内外资料了解到的,给我的体会是,很多东西,想一次就做好基本上是不可能的,比如FFmpeg,我先后做过它相关3次,每次体会到的都比以前要深,所以,多思考,多做,都理解把~~~我觉得多思考,也是程序猿进阶的方式之一。
我尽量写博文,想把自己的思考方式分享给大家,我如果要是做一个以前没接触过的东西,那么我第一件事会是去百度google他的功课,基本概念,等等,只有知道了你要做的是什么,你才能磨刀不误砍柴工~~~以上都是我个人的见解啦~如有不妥,希望大家指出,也怕误导到别人。
对于流媒体,我现在接触到使用范围是这些,1.项目中使用,比如监控项目。2.diy玩家使用,比如我现在也做做FPV机器人开发,iPad遥控的那种,所以也会涉及到摄像头。3.一些视频播放的需求项目。总之熟悉了流媒体,还是很好玩的~~
下面上一些我做的APP截图把(智能家居的两张是用FFmpeg开发的)~~~
顶一个- -、呵呵
请问你这个博客怎么搭建呀?
nice to meet you
//网络视频
-(void)netWorkVideo
{
//NSString* urlStr=[NSString stringWithFormat:@”http://192.168.1.1:8080?action=naspshot”];
NSString* urlS=@”http://192.168.1.1:8080?action=naspshot”;
NSURL* url= [NSURL URLWithString:urlS];
NSLog(@”url==%@”,url);
NSURLRequest*request=[[NSURLRequest alloc]initWithURL:url];
//通过url,获得requet;//根据request得到Connection;
NSURLConnection* myConnection=[[NSURLConnection alloc]initWithRequest:request delegate:self];
[myConnection start];
}
//每接收一段数据就会调用此函数
-(void)connection:(NSURLConnection*)connection didReceiveData:(NSData *)data
{
// NSLog(@”==didReceiveData:data==%@==”,data);
[self.allData appendData:data];
// NSLog(@”==didReceiveData==self.allData==%@==”,self.allData);
// http://zasper.net/archives/4891
NSLog(@”connectDidReceiveData-endMarkerData-%@”,endMarkerData);
NSRange endRange=[self.allData rangeOfData:endMarkerData
options:0
range:NSMakeRange(0, self.allData.length)];
long endLocation=endRange.location + endRange.length;
if(self.allData.length >= endLocation){
NSRange imageRange=NSMakeRange(0, endLocation);
NSData* imageData=[self.allData subdataWithRange:imageRange];
UIImage* receivedImage=[UIImage imageWithData:imageData];
if(receivedImage){
self.imageView.image = receivedImage;
NSLog(@”解码图片”);
}
}
}
但是---endMarkerData是空---请明示啊
2016-03-04 17:03:50.582 JIYI[2249:168200] connectDidReceiveData-endMarkerData-
2016-03-04 17:03:50.583 JIYI[2249:168200] connectDidReceiveData-endMarkerData-
请问,我使用avplayer播放一个m3u8的流媒体视频怎么截取当前帧,如果知道请告诉我,谢谢
你好,可以加QQ群151853771~
Make a more new posts please 🙂
___
Sanny