iOS硬解H.264:-VideoToolboxDemo源码分析

标签:des   算法   class   log   com   http   si   使用   代码   

来源:http://www.cnblogs.com/michaellfx/p/understanding_-VideoToolboxDemo.html

 

iOS硬解H.264:-VideoToolboxDemo源码分析[草稿]

iOS硬解H.264:-VideoToolboxDemo源码分析

-VideoToolboxDemo为VideoToolbox的简单应用示例。

1 - 初始化

技术分享

(一)初始化FFmpeg

SuperVideoFrameExtractor类提供了两个初始化方法,

  • initWithVideo:usesTcp:
  • initWithVideo:

分别对应本地文件与网络流。

为了提高响应速度,在读取网络流时,手动遍历 AVFormatContext.streams[] 字段,本地文件则调用av_find_best_stream()获取指定的音视频流。正如函数名所示,为了查找最佳信息,av_find_best_stream()需要读取更多的流数据才能返回流信息,考虑到网络传输速度不稳定,有时该函数执行时间略长。

另外,读取网络流时,打开流之前,需初始化网络和设置传输协议,由avformat_network_init() 和 av_dict_set("rtsp_transport", "tcp") 实现。

(二)视频时长与当前播放时间

视频总长度可从AVFormatContext中读取,单位微秒。

video.duration {
	return (double)pFormatCtx->duration / AV_TIME_BASE/* 1000000 */; }

duration字段说明如下

/**
 * Duration of the stream, in AV_TIME_BASE fractional
 * seconds. Only set this value if you know none of the individual stream * durations and also do not set any of them. This is deduced from the * AVStream values if not set. * * Demuxing only, set by libavformat. */

当前播放时间由AVPacket.pts字段与AVStream.time_base字段计算得出:

- (double)currentTime {
    AVRational timeBase = pFormatCtx->streams[videoStream]->time_base;
    return packet.pts * (double)timeBase.num / timeBase.den;
}

其实,有时AVPacket并无pts数据,FFmpeg会从视频帧所属的包中复制第一帧的显示时间戳给后面的帧,所以,此值不一定准确。

(三)视频刷新

使用CADisplayLink以每秒60次调用 displayLinkCallback: 方法刷新视频。此方法读取显示时间戳pts、视频转成的图片数组,在时间戳数据足够( if ([self.presentationTimes count] == 3) )时发出继续解码信号,同时在图像数据有值时转换成图片并显示在UIImageView上。

- (void)displayLinkCallback:(CADisplayLink *)sender
{
    if ([self.outputFrames count] && [self.presentationTimes count]) { CVImageBufferRef imageBuffer = NULL; NSNumber *insertionIndex = nil; id imageBufferObject = nil; @synchronized(self){ insertionIndex = [self.presentationTimes firstObject]; imageBufferObject = [self.outputFrames firstObject]; imageBuffer = (__bridge CVImageBufferRef)imageBufferObject; } @synchronized(self){ if (imageBufferObject) { [self.outputFrames removeObjectAtIndex:0]; } if (insertionIndex) { [self.presentationTimes removeObjectAtIndex:0]; if ([self.presentationTimes count] == 3) { NSLog(@"====== start ======"); dispatch_semaphore_signal(self.bufferSemaphore); } } } if (imageBuffer) { NSLog(@"====== show ====== %lu", (unsigned long)self.presentationTimes.count); [self displayImage:imageBuffer]; } } }

(四)CVImageBuffer转换成UIImage

转换算法如本节开始的流程图所示,由 displayImage: 方法实现:

  1. CVPixelBufferLockBaseAddress
    -- 获取图像内部数据 --
  2. CVPixelBufferGetWidth
  3. CVPixelBufferGetHeight
  4. CVPixelBufferGetBytesPerRow
    -- 使用Core Graphics创建CGImage --
  5. CGColorSpaceCreateDeviceRGB
  6. CGBitmapContextCreate
  7. CGColorSpaceRelease
    -- CGImage转换成UIImage --
  8. CGBitmapContextCreateImage
  9. [UIImage imageWithCGImage:]
    -- 清理操作 --
  10. CGImageRelease
  11. CGContextRelease
  12. CVPixelBufferUnlockBaseAddress

(五)获取视频播放的当前帧图片

使用VideoToolbox解码可以直接从CVPixelBuffer生成图片,然而,本项目使用FFmpeg软解实现此功能。

技术分享

A. AVFrame转换成AVPicture

sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picture.data, picture.linesize);

B. 以PPM格式保存图片至闪存(非必要操作)

-(void)savePicture:(AVPicture)pict width:(int)width height:(int)height index:(int)iFrame { FILE *pFile; NSString *fileName; int y; fileName = [Utilities documentsPath:[NSString stringWithFormat:@"image%04d.ppm",iFrame]]; // Open file NSLog(@"write image file: %@",fileName); pFile=fopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "wb"); if(pFile==NULL) return; // Write header fprintf(pFile, "P6\n%d %d\n255\n", width, height); // Write pixel data for(y=0; y<height; y++) fwrite(pict.data[0]+y*pict.linesize[0], 1, width*3, pFile); // Close file fclose(pFile); }

C. 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; }

(六)初始化音频

存在音频流时才初始化音频,由 -[SuperVideoFrameExtractor setupAudioDecoder] 完成,主要工作是分配缓冲区(_audioBuffer)内存,打开解码器和初始化AVAudioSession。

2 - 音视频输出

(一)视频

技术分享

play: 方法启动后台线程进行解码,每读取一个有效的视频包时调用VideoToolbox解码。在VideoToolbox回调函数中通过委托方法,将CVPixelBuffer转换成图片显示。

(二)音频

音频的输出由AVAudiosession实现,此项目并没实现音频输出。

3 - VideoToolbox解码

iOS 8开放了H.264硬件编解码接口VideoToolbox.framework,解码编程步骤为:

  1. VTDecompressionSessionCreate:创建解码会话
  2. VTDecompressionSessionDecodeFrame:解码一个视频帧
  3. VTDecompressionSessionInvalidate:释放解码会话

添加VideoToolbox.framework到工程并包含#include <VideoToolbox/VideoToolbox.h>开始硬解编程。

技术分享

硬解H.264前需配置VideoToolbox,简单地说,VideoToolbox只有了解输入数据源才能进行有效解码,我们要做的就是给它提 供H.264的SPS(Sequence Parameter Sets)和PPS(Picture Parameter Set)数据,创建出格式描述对象,由此创建解码会话。

(一)SPS与PPS

H.264的SPS和PPS包含了初始化H.264解码器所需要的信息参数,包括编码所用的profile,level,图像的宽和高,deblock滤波器等。

MP4或MPEG-TS格式的H.264数据可以从AVCodecContext的extradata中读取到SPS和PPS数据,如

uint8_t *data = pCodecCtx -> extradata;
int size = pCodecCtx -> extradata_size;

而以Elementary Stream形式从网络接收H.264裸流时,不存在单独的SPS、PPS包或帧,而是附加在I帧前面,存储的一般形式为 00 00 00 01 SPS 00 00 00 01 PPS 00 00 00 01 I帧,前面的这些00 00数据称为起始码(Start Code),它们不属于SPS、PPS的内容,只作标识。所以,创建CMFormatDescription时需要过滤它们。

由于VideoToolbox接口只接受MP4容器格式,当接收到Elementary Stream形式的H.264流,需把Start Code(3- or 4-Byte Header)换成Length(4-Byte Header)。

技术分享

  • Start Code表现形式:00 00 01 或 00 00 00 01
  • Length表现形式:00 00 80 00

技术分享

处理流程为 00 00 01 或 00 00 00 01 => 00 00 80 00(当前帧长度)。每个帧都需要处理。

技术分享

本项目给出URL为 rtsp://192.168.2.73:1935/vod/sample.mp4 ,它的处理方式为:

for (int i = 0; i < size; i++) {
    if (i >= 3) { if (data[i] == 0x01 && data[i-1] == 0x00 && data[i-2] == 0x00 && data[i-3] == 0x00) { if (startCodeSPSIndex == 0) { startCodeSPSIndex = i; } if (i > startCodeSPSIndex) { startCodePPSIndex = i; } } } } spsLength = startCodePPSIndex - startCodeSPSIndex - 4; ppsLength = size - (startCodePPSIndex + 1); nalu_type = ((uint8_t) data[startCodeSPSIndex + 1] & 0x1F); if (nalu_type == 7/* Sequence parameter set (non-VCL) */) { spsData = [NSData dataWithBytes:&(data[startCodeSPSIndex + 1]) length: spsLength]; } nalu_type = ((uint8_t) data[startCodePPSIndex + 1] & 0x1F); if (nalu_type == 8/* Picture parameter set (non-VCL) */) { ppsData = [NSData dataWithBytes:&(data[startCodePPSIndex + 1]) length: ppsLength]; }

按我理解,由于是MP4容器,故只需提取SPS、PPS数据。

(二)创建视频格式描述对象

CMFormatDescription描述了视频的基本信息,有时也用CMVideoFormatDescriptionRef表示,typedef CMFormatDescriptionRef CMVideoFormatDescriptionRef; ,示意图如下。

技术分享

有两个接口可创建视频格式描述对象CMFormatDescriptionRef,本项目使用了 CMVideoFormatDescriptionCreateFromH264ParameterSets ,因为前面已处理SPS及PPS。

const uint8_t* const parameterSetPointers[2] = { (const uint8_t*)[spsData bytes], (const uint8_t*)[ppsData bytes] }; const size_t parameterSetSizes[2] = { [spsData length], [ppsData length] }; status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, 4, &videoFormatDescr);

另一个接口是不处理,以atom形式提供给VideoToolbox,由它自行处理,如

CFMutableDictionaryRef atoms = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks); CFMutableDictionarySetData(atoms, CFSTR ("avcC"), (uint8_t *)extradata, extradata_size); CFMutableDictionaryRef extensions = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFMutableDictionarySetObject(extensions, CFSTR ("SampleDescriptionExtensionAtoms"), (CFTypeRef *) atoms); CMVideoFormatDescriptionCreate(NULL, format_id, width, height, extensions, &videoFormatDescr);

(三)创建解码会话

创建解码会话需要提供回调函数以便系统解码完成时将解码数据、状态等信息返回给用户。

VTDecompressionOutputCallbackRecord callback;
callback.decompressionOutputCallback = didDecompress;
callback.decompressionOutputRefCon = (__bridge void *)self;

回调函数原型为 void didDecompress( void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ),每解码一帧视频都调用一次此函数。

CVImageBufferRef与CVPixelBufferRef是同一个类型,typedef CVImageBufferRef CVPixelBufferRef ,依图像缓冲区类型,像素缓冲区为图像缓冲区提供内存存储空间。CVPixelBuffer持有图像信息的描述,如宽度、高度、像素格式类型,示意图如下:

技术分享

由于CVPixelBuffer的创建与释放属于耗性能操作,苹果提供了CVPixelBufferPool管理CVPixelBuffer,它在 后端提供了高效的CVPixelBuffer循环利用机制。CVPixelBufferPool维持了CVPixelBuffer的引用计数,当计数为0 时,将CVPixelBuffer收回它的循环利用队列,下次遇到创建CVPixelBuffer请求时,返回其中一个可用的 CVPixelBuffer,而非直接释放。

技术分享

创建解码会话时还需要提供一些解码指导信息,如已解码数据是否为OpenGL ES兼容、是否需要YUV转换RGB(此项一般不设置,OpenGL转换效率更高,VideoToolbox转换不仅需要使用更多内存,同时也消耗CPU)等等,如

NSDictionary *destinationImageBufferAttributes =[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],(id)kCVPixelBufferOpenGLESCompatibilityKey,[NSNumber numberWithInt:kCVPixelFormatType_32BGRA],(id)kCVPixelBufferPixelFormatTypeKey,nil];

准备工作都完成了,现在正式创建解码会话 VTDecompressionSessionCreate(kCFAllocatorDefault, videoFormatDescr, NULL, (CFDictionaryRef)destinationImageBufferAttributes, &callback, &session);,该会话在每次解码时都会被调用。

(四)解码

A. 解码前将AVPacket的数据(网络抽象层单元数据,NALU)拷贝到CMBlockBuffer:

int nalu_type = ((uint8_t)packet.data[startCodeIndex + 1] & 0x1F);
if (nalu_type == 1/* Coded slice of a non-IDR picture (VCL) */ || nalu_type == 5/* Coded slice of an IDR picture (VCL) */) { CMBlockBufferCreateWithMemoryBlock(NULL, packet.data, packet.size, kCFAllocatorNull, NULL, 0, packet.size, 0, &videoBlock); }

CMBlockBuffer提供一种包装任意Core Media数据块的基本办法。在视频处理流水线遇到的压缩视频数据,几乎都被包装在CMBlockBuffer中。

B. 用4字节长度代码(4 byte length code (the length of the NalUnit including the unit code))替换分隔码(separator code)

int reomveHeaderSize = packet.size - 4;
const uint8_t sourceBytes[] = {(uint8_t)(reomveHeaderSize >> 24), (uint8_t)(reomveHeaderSize >> 16), (uint8_t)(reomveHeaderSize >> 8), (uint8_t)reomveHeaderSize}; status = CMBlockBufferReplaceDataBytes(sourceBytes, videoBlock, 0, 4);

C. 由CMBlockBuffer创建CMSampleBuffer

const size_t sampleSizeArray[] = {packet.size};
CMSampleBufferCreate(kCFAllocatorDefault, videoBlock, true, NULL, NULL, videoFormatDescr, 1, 0, NULL/* 可传递CMSampleTimingInfo */, 1, sampleSizeArray, &sbRef);

CMSampleBuffer包装了数据采样,就视频而言,CMSampleBuffer可包装压缩视频帧或未压缩视频帧,它组合了如下类 型:CMTime(采样的显示时间)、CMVideoFormatDescription(描述了CMSampleBuffer包含的数据)、 CMBlockBuffer(对于压缩视频帧)、CMSampleBuffer(未压缩光栅化图像,可能包含在CVPixelBuffer或 CMBlockBuffer),如图所示。

技术分享

D. 解码

VideoToolbox支持同、异步解码,由VTDecodeFrameFlags指定,VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression; ,默认为同步解码。

同步解码时,调用解码函数 VTDecompressionSessionDecodeFrame 后系统回调我们提供的回调函数,然后解码函数才结束调用。异步解码则回调顺序不确定,故需要自行整理帧序。

VTDecompressionSessionDecodeFrame(session, sbRef, flags, &sbRef, &flagOut);

(五)时间

项目有段未使用的代码,如下所示:

int32_t timeSpan = 90000;
CMSampleTimingInfo timingInfo;
timingInfo.presentationTimeStamp = CMTimeMake(0, timeSpan);
timingInfo.duration = CMTimeMake(3000, timeSpan); timingInfo.decodeTimeStamp = kCMTimeInvalid;

因为CMSampleBuffer只有图片数据,无时间信息,需要在解码时提供额外的时间说明。

CMTime是VideoToolbox中关于时间的基本描述,示意图如下。

技术分享

由于CMTime一直在增长,不好控制,苹果提供了易于控制的CMTimebase。

技术分享

(六)刷新正在解码的视频帧

/* Flush in-process frames. */
VTDecompressionSessionFinishDelayedFrames(session);
/* Block until our callback has been called with the last frame. */
VTDecompressionSessionWaitForAsynchronousFrames(session);

(七)清理资源

/* Clean up. */
VTDecompressionSessionInvalidate(session);
CFRelease(session);
CFRelease(videoFormatDescr);

4 - 总结

5 - 附录

输出AVPacket

- (void) dumpPacketData
{
    // Log dump
    int index = 0;
    NSString *tmp = [NSString new]; for(int i = 0; i < packet.size; i++) { NSString *str = [NSString stringWithFormat:@" %.2X",packet.data[i]]; if (i == 4) { NSString *header = [NSString stringWithFormat:@"%.2X",packet.data[i]]; NSLog(@" header ====>> %@",header); if ([header isEqualToString:@"41"]) { NSLog(@"P Frame"); } if ([header isEqualToString:@"65"]) { NSLog(@"I Frame"); } } tmp = [tmp stringByAppendingString:str]; index++; if (index == 16) { NSLog(@"%@",tmp); tmp = @""; index = 0; } } }

内存清理

// Free scaler
sws_freeContext(img_convert_ctx);

// Free RGB picture
avpicture_free(&picture); // Free the packet that was allocated by av_read_frame av_free_packet(&packet); // Free the YUV frame av_free(pFrame); // Close the codec if (pCodecCtx) avcodec_close(pCodecCtx); // Close the video file if (pFormatCtx) avformat_close_input(&pFormatCtx);

定位到指定时间关键帧

- (void)seekTime:(double)seconds {
    AVRational timeBase = pFormatCtx->streams[videoStream]->time_base;
    int64_t targetFrame = (int64_t)((double)timeBase.den / timeBase.num * seconds); avformat_seek_file(pFormatCtx, videoStream, targetFrame, targetFrame, targetFrame, AVSEEK_FLAG_FRAME); avcodec_flush_buffers(pCodecCtx); }

H.264基础

技术分享

SPS、PPS一旦设置,将被用于后续的NALU,只有更新它才会影响新的NALU解码,和OpenGL一样,状态设置后一直保持。

对于Elementary Stream,SPS和PPS包含在一个NALU中,方便回话。MP4则将它提取出来,放在文件头部,这样可支持随机访问。

技术分享

Elementary Stream与MP4容器格式之类的SPS、PPS转换。

技术分享

不同容器格式的NAL Unit头之间的区别。

技术分享

转换方法是,将Elementary Stream起始码换成长度,这在前面有描述。

AVSampleBufferDisplay

前面说过,CMTime难以控制,所以AVSampleBufferDisplay提供了CMTimebase的控制方式。

技术分享

SPS与PPS

0x67是SPS的NAL头,0x68是PPS的NAL头,举例:

[000]=0x00 [001]=0x00 [002]=0x00 [003]=0x01 [004]=0x67 [005]=0x64 [006]=0x00 [007]=0x32 [008]=0xAC [009]=0xB3 [010]=0x00 [011]=0xF0 [012]=0x04 [013]=0x4F [014]=0xCB [015]=0x08 [016]=0x00 [017]=0x00 [018]=0x03 [019]=0x00 [020]=0x08 [021]=0x00 [022]=0x00 [023]=0x03 [024]=0x01 [025]=0x94 [026]=0x78 [027]=0xC1 [028]=0x93 [029]=0x40 [030]=0x00 [031]=0x00 [032]=0x00 [033]=0x01 [034]=0x68 [035]=0xE9 [036]=0x73 [037]=0x2C [038]=0x8B [039]=0x00 [040]=0x00 [041]=0x01 [042]=0x65

成分为:

Start Code:0x00 0x00 0x00 0x01

SPS从[004]开始,长度为24:


0x67 0x64 0x00 0x32 0xAC 0xB3 0x00 0xF0 0x04 0x4F 0xCB 0x08 0x00 0x00 0x03 0x00 0x08 0x00 0x00 0x03 0x01 0x94 0x78 0xC1 0x93 0x40

PPS从[034]开始,长度为5:0x68 0xE9 0x73 0x2C 0x8B

SPS内容:

profile_idc = 66
constrained_set0_flag = 1
constrained_set1_flag = 1
constrained_set2_flag = 1
constrained_set3_flag = 0
level_idc = 20
seq_parameter_set_id = 0
chroma_format_idc = 1
bit_depth_luma_minus8 = 0
bit_depth_chroma_minus8 = 0
seq_scaling_matrix_present_flag = 0
log2_max_frame_num_minus4 = 0
pic_order_cnt_type = 2
log2_max_pic_order_cnt_lsb_minus4 = 0
delta_pic_order_always_zero_flag = 0
offset_for_non_ref_pic = 0
offset_for_top_to_bottom_field = 0
num_ref_frames_in_pic_order_cnt_cycle = 0
num_ref_frames = 1
gaps_in_frame_num_value_allowed_flag = 0
pic_width_in_mbs_minus1 = 21
pic_height_in_mbs_minus1 = 17
frame_mbs_only_flag = 1
mb_adaptive_frame_field_flag = 0
direct_8x8_interence_flag = 0
frame_cropping_flag = 0
frame_cropping_rect_left_offset = 0
frame_cropping_rect_right_offset = 0
frame_cropping_rect_top_offset = 0
frame_cropping_rect_bottom_offset = 0
vui_parameters_present_flag = 0

pic_width_in_mbs_minus1 = 21
pic_height_in_mbs_minus1 = 17
分别表示图像的宽和高,以宏块(16x16)为单位的值减1
因此,实际的宽为 (21+1)*16 = 352

PPS内容:

pic_parameter_set_id = 0
seq_parameter_set_id = 0
entropy_coding_mode_flag = 0
pic_order_present_flag = 0
num_slice_groups_minus1 = 0
slice_group_map_type = 0
num_ref_idx_l0_active_minus1 = 0
num_ref_idx_l1_active_minus1 = 0
weighted_pref_flag = 0
weighted_bipred_idc = 0
pic_init_qp_minus26 = 0
pic_init_qs_minus26 = 0
chroma_qp_index_offset = 10
deblocking_filter_control_present_flag = 1
constrained_intra_pred_flag = 0
redundant_pic_cnt_present_flag = 0
transform_8x8_mode_flag = 0
pic_scaling_matrix_present_flag = 0
second_chroma_qp_index_offset = 10

分析方法

67 42 e0 0a 89 95 42 c1 2c 80 (67为sps头)

0100 0010 1110 0000 0000 1010 1000 1001 1001 0101 0100 0010 11000001 0010 1100 1000 0000

FIELDNo. of BITSVALUECodeNum描述符
profile_idc80100001066u(8)
constraint_set0_flag11 u(1)
constraint_set1_flag11 u(1)
constraint_set2_flag11 u(1)
constraint_set3_flag10 u(1)
reserved_zero_4bits40000 u(4)
level_idc80000101010u(8)
seq_parameter_set_id110ue(v)
log2_max_frame_num_minus4700010018ue(v)
pic_order_cnt_type110ue(v)
log2_max_pic_order_cnt_lsb_minus45001014ue(v)
num_ref_frames3010 ue(v)
gaps_in_frame_num_value_allowed_flag11 u(1)
pic_width_in_mbs_minus1900001011020ue(v)
pic_height_in_map_units_minus1900001001016ue(v)
frame_mbs_only_flag110u(1)
mb_adaptive_frame_field_flag110u(1)
direct_8x8_inference_flag10 u(1)
frame_cropping_flag10 u(1)
vui_parameters_present_flag110u(1)

68 ce 05 8b 72 (68为pps头)
1100 1110 0000 0101 1000 1011 0111 0010 pps

FIELDNo. of BITSVALUECodeNum描述符
pic_parameter_set_id110ue(v)
seq_parameter_set_id110ue(v)
entropy_coding_mode_flag10 ue(1)
pic_order_present_flag10 ue(1)
num_slice_groups_minus1110ue(v)
num_ref_idx_l0_active_minus1110ue(v)
num_ref_idx_l1_active_minus1110ue(v)
weighted_pred_flag10 ue(1)
weighted_bipred_idc200 ue(2)
pic_init_qp_minus267000101110(-5)se(v)
pic_init_qs_minus267000101110(-5)se(v)
chroma_qp_index_offset30112(-1)se(v)
deblocking_filter_control_present_flag11 ue(1)
constrained_intra_pred_flag10 ue(1)
redundant_pic_cnt_present_flag10 ue(1)

长度与起始码

rtsp

MP4

MP4封装格式对应标准为 ISO/IEC 14496-12(信息技术 视听对象编码的第12部分: ISO 基本媒体文件格式/Information technology Coding of audio-visual objects Part 12: ISO base media file format)

MP4封装格式是基于QuickTime容器格式定义,媒体描述与媒体数据分开,目前被广泛应用于封装h.264视频和ACC音频,是高清视频/HDV的代表。

参考

iOS硬解H.264:-VideoToolboxDemo源码分析[草稿]

标签:des   算法   class   log   com   http   si   使用   代码   

原文:http://www.cnblogs.com/sunminmin/p/4976418.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 黑群晖是一种运行在普通计算机上的NAS系统,提供了丰富的网络存储和应用服务。6.2表示黑群晖的版本号,而j1900是一种处理器型号。 硬解补丁extra.lzma是为了提升黑群晖运行效能而开发的一种补丁。它主要适用于使用j1900处理器的硬件设备。 在使用过程中,许多黑群晖用户发现在使用j1900处理器时,系统的解码能力不够高,导致一些高清视频或图形应用无法流畅播放。为了解决这个问题,黑群晖团队研发了extra.lzma硬解补丁。 这个补丁的作用是优化黑群晖系统对j1900处理器的硬件解码功能,通过充分利用处理器的资源提升解码速度和效能。使用extra.lzma补丁后,黑群晖系统可以更好地支持高清视频播放和图形应用。 为了使用这个补丁,用户需要进行一些操作。首先,确保黑群晖系统的版本是6.2,并且使用j1900处理器的硬件设备。然后,下载并解压缩extra.lzma补丁文件。接下来,将补丁文件放置在指定的位置,并按照黑群晖官方的说明进行配置和安装。 值得注意的是,安装补丁可能会对系统造成一些风险,因此在安装之前请备份重要的数据和配置文件。如果有任何问题或疑问,建议咨询黑群晖官方支持或相关论坛。 经过使用extra.lzma硬解补丁后,黑群晖用户可以更好地享受高清视频和图形应用,提升系统性能和用户体验。 ### 回答2: 黑群晖6.2 j1900硬解补丁extra.lzma是为了支持硬解码而开发的一个补丁文件。黑群晖是一款在个人电脑上运行的软件,它允许用户将电脑变成一个网络存储设备,用来共享文件、数据备份等。然而,一些旧款硬件设备可能无法完全支持黑群晖的功能,尤其是在视频播放和解码方面。 为了解决这个问题,黑群晖开发了extra.lzma补丁,为支持硬解码而设计。这个补丁特别适用于j1900系列的硬件设备。它能够提供更好的视频播放性能和更高的解码效率。通过安装这个补丁,用户可以在黑群晖上流畅播放高清视频,而无需使用软件解码,降低对CPU的压力,提高系统性能。 extra.lzma补丁的安装相对简单,用户只需下载适用于自己设备的版本,然后将其上传至黑群晖控制面板上的DSM,通过选项安装即可。安装完成后,用户可以在黑群晖上享受更好的视频播放效果。 总之,黑群晖6.2 j1900硬解补丁extra.lzma是一个为了提供更好的视频播放性能而开发的补丁文件。安装这个补丁后,用户将能够在黑群晖上流畅播放高清视频,并提高系统性能。 ### 回答3: 黑群晖6.2 j1900硬解补丁extra.lzma是为了在硬件上支持更高级的解码功能而引入的插件。黑群晖是指在个人电脑上运行的名为Synology DiskStation Manager(DSM)操作系统的非官方版本。 J1900是一款由Intel推出的四核处理器,该处理器以其低功耗和合理的性能而受到很多人的喜爱。然而,由于原生的黑群晖系统不支持硬解码功能,这限制了某些高级的媒体播放功能。 为了解决这个问题,黑群晖社区开发者提供了extra.lzma硬解补丁。这个补丁可以通过修改黑群晖引导器来启用硬解功能。extra.lzma能够实现硬件加速解码的优势,可以使显卡或处理器负责解码高清视频,大大提高了视听体验。 要将硬解补丁extra.lzma应用到黑群晖6.2 j1900系统中,首先需要将extra.lzma文件上传到黑群晖系统的安装介质中。然后,在黑群晖安装过程中选择"自定义安装"并进入"高级选项"菜单。在该菜单中,用户可以选择"加载额外的驱动程序",并选择上传的extra.lzma文件。之后,继续按照安装向导的指示完成黑群晖系统的安装。 通过安装extra.lzma硬解补丁,用户可以在黑群晖6.2 j1900系统中享受到更出色的媒体播放体验。这样一来,用户可以更流畅地观看高清视频,同时也能够更好地处理其他媒体相关任务。总之,extra.lzma硬解补丁为黑群晖6.2 j1900系统的用户带来了更多的功能和便利。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值