iOS 平台下写网络视频监视器中的总结

来源:互联网 发布:冷轩网络 编辑:程序博客网 时间:2024/04/29 04:10

以下是为了了解自己这段时间的学习情况,而进行的总结, 目的是为了完成从某服务器得到数据,在iOS设备上进行解析,并完成实时播放的目的。

其中,使用到 ffmpeg, GCDAsyncSocket, wireShark工具


1. 首先需要实现在本地播放视频文件,这里指的是h264格式的视频格式。需要采用到ffmpeg来进行解析播放,具体如何配置ffmpeg,我在前面的文章中已经提到。


2. 通过GCDAsyncSocket来连接服务器,并通过wireShark来查看其中传递的命令是否正确。

遇到两个问题:

a. 心跳的问题, 

在用GCDAsyncSocket与服务器连接时,通常对于用Socket方式的服务器,由于Socket的连接会比较耗资源,所以服务器一般都会要求客户端在一定时期内进行心跳检测,以让服务器知道客户端已经断开连接,从而回收这个Socket资源,以供其它Socket使用。而这个心跳命令,是每个服务器都会自己定义自己的心跳格式。我遇到这个问题的征状是,建立完成GCDAsyncSocket连接后,约2分钟, GCDAsyncSocket的delegate会收到方法, 

说是,连接被远端断开,

failed because Error 
Domain=GCDAsyncSocketErrorDomain Code=7 "Socket closed by remote peer" 
UserInfo=0x7f85d8d1dd50 {NSLocalizedDescription=Socket closed by 
remote peer} 

方法“- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err”被调用,这个可以说明其实就是因为心跳没有连接好,以致于服务器认为客户端已经断开了连接,

认识到这个原因后,通过wireShark来分析正常的客户端应该发送什么样的数据,然后再看看自己的客户端发送的什么数据,比对后,很容易找到问题,拼出服务器期望的数据后,心跳的这个问题

b. 在连接的过程上,

if (![_controlSocketconnectToHost:SOCKET_HOSTonPort:SOCKET_CONTROL_PORTerror:&control_error])

调用正常,返回值正常,但很快就收到方法通知

ocketDidDisconnect:0x834d5d0 withError: Error Domain=NSPOSIXErrorDomain Code=61 "Connection refused" UserInfo=0x6c3f5c0 {NSLocalizedFailureReason=Error in connect() function, NSLocalizedDescription=Connection refused}

2012-03-20 14:46:21.795 CameraSeek[945:f803] socket Disconnected

结果又纠结了半天, 后来才发现,因为我回家里进行了调试,改动了SOCKET_HOST这个IP值,以致于在建立这个Socket时,出错。 真是个呆子啊




下面分析一下FFMpeg是如何把视频流解析成各个UIImage的

(上一篇文章已经讲了如何使ffmpeg运行在模拟器或者手机上,下面只侧重于讲ffmpeg是如何工作的)

通道一个项目中只会有一个解码器,但对于分屏显示除外,对于手机终端来说,一般是不是进行分屏显示,所以整个项目一般只需要一个这样的解码器, 所以通常只需要建立一个单例解析类就行了

在初始化这个单例类时,先进行格式注册和编码注册。

主线:视频原始数据-->FFMPeg -->YUV-->RGB-->UIImage

- (id)initForNetH264StreamWidth:(NSInteger)vedioShowWidth andLength:(NSInteger)vedioShowWHeight {

    av_register_all();

   avcodec_register_all();

    // 一般我们只需要像上面这样写即可,把所有能支持的都向系统进行注册。

    // 然后进行分配内存

    // Allocate video frame

    AVFrame *pFrame=avcodec_alloc_frame();

    AVCodecContext *pCodecCtx =avcodec_alloc_context();

   AVCodec *codecid =avcodec_find_decoder(CODEC_ID_H264);

    // 由于这里仅需要对H264的格式进行解码,所以这里直接指定其查找的格式


    // 然后判断这里是否支持H264的编码,看能否打开即可知道

    if(avcodec_open(pCodecCtx,codecid) <0) {

       NSLog(@"avcodec_open error, can't open video codec....");

        goto initError;

    }

    //下面的宽度是来自于其实应该是使用来自服务器回复的原始视频尺寸,这里为了简单举例,所以直接给定原始视频尺寸

   pCodecCtx->width =352;

   pCodecCtx->height =288;

   pCodecCtx->pix_fmt =PIX_FMT_YUV420P;// 这里使用了YUV视频格式,具体后面再谈

    

   // Release old picture and scaler

avpicture_free(&picture);

sws_freeContext(img_convert_ctx);

    //下面的picture将会存储进行缩放后的数据,所以给其带的参数将在后面生成UIImage时予以指定,存储生成后的图片大小,将用来进行指定解码后的图像尺寸

    viewVedioSize = CGSizeMake(vedioShowWidth, vedioShowWHeight);

    

    // Allocate RGB picture,所以在这里进行picturealloc时会指定其宽度及高度

         avpicture_alloc(&picture,PIX_FMT_RGB24,viewVedioSize.width,viewVedioSize.height);

    // Setup scaler// 下面这两步非常重要,是进行视频尺寸缩放的关键

    staticint sws_flags = SWS_FAST_BILINEAR;

    img_convert_ctx =sws_getContext(pCodecCtx->width

pCodecCtx->height,

pCodecCtx->pix_fmt,

 viewVedioSize.width,          //生成图像的宽度

viewVedioSize.height,         //生成图像的高度

PIX_FMT_RGB24,

sws_flags,NULL,NULL,NULL);

}

// 上面已经所环境建立起来了, 下面使用上面的这个解码器来进行解码


// 当有数据过来后,调用下面这个方法来生成UIImage

// 这时传入的数据是一个完整的I帧包或者完整的P帧包,总之是一个完整的可以显示数据的帧包

- (UIImage *)decodeWithMediaPureData:(NSData *)pureMediaData {

    Byte *bytesMediaData = (Byte *)[pureMediaDatabytes];


    // 把到来的数据,放置在AVPacket之中

    AVPacket packet;

    av_init_packet(&packet);

    packet.data = bytesMediaData;

    packet.size = pureMediaData.length;

    

   //把数据从packet写到pFrame

    int got_picture_ptr=0;

    int nImageSize = avcodec_decode_video2(pCodecCtx,pFrame, &got_picture_ptr, &packet);

    av_free_packet(&packet);

    

    // 下面使用前面初始化时设定的原始数据以及指定输出的UIImage尺寸大小进行缩放

    if((nImageSize>0) && (pFrame->data[0]!=nil))

    {                

       //把数据从pFrame写到picture之中

       sws_scale (img_convert_ctx

                   pFrame->data

                   pFrame->linesize,

                   0

                   pCodecCtx->height,

                   picture.data

                   picture.linesize);

        

       //数据已经到picture之中了,把picture转化为UIImage进行输出即可

       //这里传入的widthheight是依据picture中的数据宽度来定的,

       //是在前面avpicture_alloc(&picture, PIX_FMT_RGB24, vedioShowWidth, vedioShowWHeight);时决定

        UIImage *finalImage = [self imageFromAVPicture:picture width:viewVedioSize.widthheight:viewVedioSize.height];

        return finalImage;        

    }else {

       NSLog(@"Error when decode video, image is nil");

        return nil;

    }

}


上面调用完成后,就生成了UIImage, 通过主线程的OpenGL或者直接使用UIKit.framework的方式直接显示在界面上, 这个主要根据业务需求,以及处理的复杂度

现在说说上面调用的imageFromAVPicture方法,由于现在数据已经被缩放后,放在AVPicture之中,所以下面这里传入的方法中width和height其实是生成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();

    int bitsPerComponent = 8;              // 8位存储一个Component

    int bitsPerPixel = 3 * bitsPerComponent;         // RGB存储,只用三个字节,而不是像RGBA要用4个字节,所以这里一个像素点要38位来存储

// 这里3个字节是来自于 PIX_FMT_RGB24的定义中说明的, 是一个24位的数据,其中RGB各占8位

这里// PIX_FMT_RGB24,    ///< packed RGB 8:8:8, 24bpp, RGBRGB...


    int bytesPerRow =3 * width;           // 每行有width个象素点,每个点用3个字节,另外注意:pict.linesize[0]=bytesPerRow=1056

CGImageRef cgImage =CGImageCreate(width, 

  height, 

  bitsPerComponent, 

  bitsPerPixel, 

  bytesPerRow,//pict.linesize[0],等效

  colorSpace, 

  bitmapInfo, 

  provider, 

 NULL

 NO

 kCGRenderingIntentDefault);

CGColorSpaceRelease(colorSpace);

UIImage *image = [UIImageimageWithCGImage:cgImage];

CGImageRelease(cgImage);

CGDataProviderRelease(provider);

CFRelease(data);

return image;

}


把得到的这个UIImage显示出来, 即完成了把H264数据转化成RGB显示出来的过程。再总结一下整个过程如下:

NSData --> (填充)AVPacket -->(avcodec_decode_video2)AVFrame --> (通过sws_scale转化为)AVPicture --> 转化为UIImage

说明:出于对我费时费力的把这部分内容整理出来,欢迎转载,但请注明出处,谢谢。


原创粉丝点击