第一次写博客心情好激动。。。。
大家都知道QT跨平台,在QT下写了一个解码FFMPEG的案例,能显示,但是性能不好。用的是FFMPEG那套大家都知悉的软解码。
ios硬解码多的是object-c写的,(我基本没有看到有c++写的),这就很坑爹了。。。因为本人工程就是QTc++!!!
然后硬着头皮看了一下各种莫名其妙符号的object-c;摸索了一下怎么在QT里面写oc的代码,没想到成功了!!!
来,上代码分析:(怎么贴图啊 ,哈哈,尴尬)首先记录一下oc编程,然后记录c++调用oc,最后记录怎么qt下实现ios硬解码
内容挺多,慢慢来看。。。
c++与o混合编程,你需要将文件名称定义为:*.mm
然后在.mm文件里面,写下你要的oc类:
@interface OCClass : NSObject
-(void) receivedRawVideoFrame: (NSString *)path ;
-(void) createDecompSession;
@property (nonatomic, copy) VideoDecodeCompleteBlock completeBlock;
-(BOOL)initH264Decoder ;
- (BOOL)readStream;
@property VTDecompressionSessionRef decodeSession;
@property CMFormatDescriptionRef formatDescription;
@property CVPixelBufferRef pixelBuffer;
@property uint8_t *buffer;
@property long bufferSize;
@property long maxSize;
- (void)setupDecodeSession;
-(CVPixelBufferRef)decode;
@property uint8_t *frame_buffer;
@property long frame_size;
@property long sps_size;
@property uint8_t *pps_buffer;
@property long pps_size;
@property uint8_t *sps_buffer;
@property (nonatomic, strong) NSInputStream *inputStream;
@end
写惯c++的是看起来很别扭对吧, @interface OCClass : NSObject这个就是c++中定义一个类继承自NSObject
@property 后面定义成员变量,可以跟c++或者oc的类型都可以。。。注意!!!!坑爹的是使用,你需要加_成员变量,
例如你需要用pps_buffer这个变量,实际上应该写_pps_buffer;- (void)setupDecodeSession;-代表非静态方法;
oc里面的类对象都只能是new出来的。类定义后面需要加@end;@这个在oc很常见,例如区分c++字符串的时候;
好哒,有c功底的很容易看懂的,我就献丑啦~!!
QT c++怎么调用OC呢,当时走了很多弯路。。我试过用xcode建立.h和.m文件来定义和实现类。在qT下添加进来,然后include
这个.h文件。结果不出意外的它不认!!!搜了一下混合编程,于是便有了现在的.mm文件。
下面两个接口是.mm文件的。它的作用是.cpp文件创建对象,用对象去调用oc写的接口.
void hardDecode(char* fileUrl,void *obj)
{
OCClass *object = (OCClass*)obj;
NSString *path = [[NSString alloc] initWithCString:(const char*)fileUrl encoding:NSASCIIStringEncoding];
[object receivedRawVideoFrame:path];
}
void* createObj()
{
OCClass *obj = [[OCClass alloc]init];
return (void*)obj;
}
在cpp里面extern一下这两个接口,杠杠的给力~!
extern void hardDecode(char* fileUrl,void *obj);
extern void* createObj();
万能的void*解决oc和c++的指针在传递过程中的转变~~
FFmpengWorker::FFmpengWorker(QObject *parent) : QObject(parent)
{
void* m_hardobj = createObj();
}
cpp构造中就把这个occlass类对象创建出来,将对象作为参数又传到接口里,就可以调用oc的接口啦~!!酸爽是不是!
std::string str = fileUrl.toStdString();
char* url = (char*)str.c_str();
hardDecode(url,m_hardobj);
这样就转到.mm文件里面去调用hardDecode这个接口了,上面对这个接口实现,void*可以转换回来occlass*了(感谢void*
),补充一下oc函数调用,怕你们看的难受。。 [[OCClass alloc]init];这个就是调用alloc接口并初始化;[]你可以认为是函数
调用,传参也好奇怪的说。。 [object receivedRawVideoFrame:path];就是调用receivedRawVideoFrame接口并
传入path的参数,传参需要用:;第一个参数不需要说明,第二三以后的还需要加一个类似说明的标志。。。坑爹吧。。。
看: NSString *path = [[NSString alloc] initWithCString:(const char*)fileUrl encoding:NSASCIIStringEncoding];
这个fileUrl是第一个参数,NSASCIIStringEncoding为第二个参数,前面是不是加了个encoding!这个把char*
转换成NSString*;
上面有函数的原型,实现-(void) receivedRawVideoFrame: (NSString *)path{}
这个里面可以发挥你天马行空的c++思维,c++和oc都可以在这里面编译过。
来堆一堆{}里面的代码
self.inputStream = [NSInputStream inputStreamWithFileAtPath:path];
[self.inputStream open];
_bufferSize = 0;
_maxSize = 10000*1000;
_buffer =(uint8_t*)malloc(_maxSize);
while(true)
{
if([self readStream]==NO)
{
NSLog(@"播放结束");
break;
}
uint32_t nalSize = (uint32_t)(_frame_size - 4);
uint32_t *pNalSize = (uint32_t *)_frame_buffer;
*pNalSize = CFSwapInt32HostToBig(nalSize);
//NAL的类型(startCode后的第一个字节的后5位)
int NAL_type = _frame_buffer[4] & 0x1f;
switch (NAL_type) {
case 0x5:
NSLog(@"Nal type is IDR frame");
if (!_decodeSession){
[self setupDecodeSession];
}
_pixelBuffer = [self decode];
break;
case 0x7:
NSLog(@"Nal type is SPS");
//从帧中获取sps信息
_sps_size = _frame_size-4;
if (!_sps_buffer){
_sps_buffer = (uint8_t*)malloc(_sps_size);
}
memcpy(_sps_buffer, _frame_buffer+4, _sps_size);
break;
case 0x8:
NSLog(@"Nal type is PPS");
//从帧中获取sps信息
_pps_size = _frame_size-4;
if (!_pps_buffer){
_pps_buffer = (uint8_t*)malloc(_pps_size);
}
memcpy(_pps_buffer, _frame_buffer+4, _pps_size);
break;
default:
//图像信息
NSLog(@"Nal type is B/P frame or another");
_pixelBuffer = [self decode];
break;
}
}
酸爽的有木有!
我不知道专写oc的怎么看的,反正我这个c++是硬着头皮看的:
大体的意思是打开这个文件,读数据 ;
- (BOOL)readStream{
if (_bufferSize<_maxSize && self.inputStream.hasBytesAvailable) {
//正数:读取的字节数,0:读取到尾部,-1:读取错误
NSInteger readSize = [self.inputStream read:_buffer+_bufferSize maxLength:_maxSize-_bufferSize];
_bufferSize += readSize;
}
//对比buffer的前四位是否是startCode(每一帧前都有startCode),并且数据长度需要大于startCode
if (memcmp(_buffer, startCode, 4) == 0 && _bufferSize > 4){
//buffer的起始和结束位置
uint8_t *startPoint = _buffer + 4;
uint8_t *endPoint = _buffer + _bufferSize;
int i=0;
while (startPoint != endPoint) {
i++;
//获取当前帧长度(通过获取到下一个0x00000001,来确定)
if (memcmp(startPoint, startCode, 4) == 0){
//找到下一帧,计算帧长
_frame_size = startPoint - _buffer;
//置空帧
if (_frame_buffer){
free(_frame_buffer);
_frame_buffer = NULL;
}
_frame_buffer = (uint8_t *)malloc(_frame_size);
//从缓冲区内复制当前帧长度的信息赋值给帧
memcpy(_frame_buffer, _buffer, _frame_size);
uint8_t *tt=_frame_buffer;
uint8_t* ttt = _buffer;
uint8_t siz = _frame_size;
//缓冲区中数据去掉帧数据(长度减少,地址移动)
memmove(_buffer, _buffer+_frame_size, _bufferSize-_frame_size);
uint8_t* tttt = _buffer;
_bufferSize -= _frame_size;
uint32_t sizt= _bufferSize;
return YES;
}else{
//如果不是,移动指针
startPoint++;
}
}
//置空帧
}
return NO;
}
好吧有木有必要分析一下怎么解析读进来的例如.h264的数据。。。
....00000001 *7........00000001 *8..........00000001*6.........00000001*5........
一定要耐着性子看:读进来的数据就是上面一堆二进制,上面的readstream教你怎么把这个数据截取
7,8,6,5,1就是你要的爱。。。你会从中得到sps,pps,时间,等等你想要的,因为这些你可以调用oc里面的接口可以得到
你想要的包含有yuv的 CVPixelBufferRef 数据啊 !!瞬间激动有木有!
这里经历了好多坑~,重点在 CFDictionaryRef怎么填写,会直接决定你解出来的CVPixelBufferRef存放yuv的结构,
网上好多都是直接调用苹果的自己的player接口可以播放,只要你得到CMBlockBufferRef数据,按照它的参数往里面传。
但是我想直接要他的yuv呢!!!时间不多,先吃饭,再继续
补充一下硬解码你需要的qt Framework怎么写哈:
在.pro文件里面
QMAKE_LFLAGS += -framework VideoToolbox
QMAKE_LFLAGS += -framework CoreMedia
QMAKE_LFLAGS += -framework CoreVideo
QMAKE_LFLAGS += -framework CoreFoundation
搞几句这么暴力的代码。
感谢一颗成长道路的向日葵Qing。