#include "rm_format_mov.h" #if (FILE_FORMAT_MOV) #include #include #include #include #if (RM_MODEL_EMT_KR) #include #include #endif // RM_MODEL_EMT_KR // NMEA PARSER #if (RM_MODEL == RM_MODEL_TYPE_ADT_CAPS || \ RM_MODEL == RM_MODEL_TYPE_BV2000 || \ RM_MODEL == RM_MODEL_TYPE_KEIYO1 || \ RM_MODEL == RM_MODEL_TYPE_MBJ5010 || \ RM_MODEL_EMT_KR ||\ RM_MODEL == RM_MODEL_TYPE_FC_DR232W) #include "rm_sensordata.h" #include "fm_parse_gps.h" #endif #if !defined(SWAP_4BYTE) #define SWAP_4BYTE(num) (((num>>24)&0xff) | ((num<<8)&0xff0000) | ((num>>8)&0xff00) | ((num<<24)&0xff000000)) #endif #define B2L_LONG(cs) ((((long)cs[0])<<24)|(((long)cs[1])<<16)|(((long)cs[2])<<8)|(((long)cs[3]))) #if (RM_MODEL == RM_MODEL_TYPE_TB4000) #include #include "../rm_app.h" #endif // TB4000 //long inline rm_byte_to_long(const unsigned char* tag) //{ // return ((((long)tag[0])<<24)|(((long)tag[1])<<16)|(((long)tag[2])<<8)|(((long)tag[3]))); //} void *memmem(const void *src, size_t src_len, const void * const search, const size_t search_len) { if (src == NULL) return NULL; // or assert(haystack != NULL); if (src_len == 0) return NULL; if (search == NULL) return NULL; // or assert(needle != NULL); if (search_len == 0) return NULL; for (const char *h = (const char*)src; src_len >= search_len; ++h, --src_len) { if (!memcmp(h, search, search_len)) { return (void*)h; } } return NULL; } //void inline rm_long_to_byte(long value, unsigned char* byte) //{ // byte[0] = ((value >> 24) & 0xFF); // byte[1] = ((value >> 16) & 0xFF); // byte[2] = ((value >> 8) & 0xFF); // byte[3] = ((value) & 0xFF); //} // 파일타입: "ftyp" // 컨테이너 타입: "moov", "trak", "udta", "tref", "imap", "mdia", "minf", "stbl", "edts", "mdra", "rmra", "imag", "vnrp", "dinf" /* const long g_containerTypes[] = { B2L_LONG("moov"), B2L_LONG("trak"), B2L_LONG("udta"), B2L_LONG("tref"), B2L_LONG("imap"), B2L_LONG("mdia"), B2L_LONG("minf"), B2L_LONG("stbl"), B2L_LONG("edts"), B2L_LONG("mdra"), B2L_LONG("rmra"), B2L_LONG("imag"), B2L_LONG("vnrp"), B2L_LONG("dinf"), //B2L_LONG("stsd"), // -> avc1 }; */ // normal size long inline rm_byte_to_long(const unsigned char* tag) { return ((((long)tag[0])<<24)|(((long)tag[1])<<16)|(((long)tag[2])<<8)|(((long)tag[3]))); } long inline rm_read_number(RMfile file) { unsigned char buffer[5] = {0,}; RMfread(buffer,4,1,file); return rm_byte_to_long(buffer); } long inline rm_byte16_to_long(const unsigned char* tag) { return ((((long)tag[0])<<8)|(((long)tag[1]))); } // extended size long inline rm_byte64_to_long(const unsigned char* tag) { return (long) ((((int64_t)tag[0])<<56)| \ (((int64_t)tag[1])<<48)| \ (((int64_t)tag[2])<<40)| \ (((int64_t)tag[3])<<32)| \ (((int64_t)tag[4])<<24)| \ (((int64_t)tag[5])<<16)| \ (((int64_t)tag[6])<<8) | \ (((int64_t)tag[7])<<0)); } void inline rm_long_to_byte(long value, unsigned char* byte) { byte[0] = ((value >> 24) & 0xFF); byte[1] = ((value >> 16) & 0xFF); byte[2] = ((value >> 8) & 0xFF); byte[3] = ((value) & 0xFF); } const long MOV_HEADER_ATOM = B2L_LONG("mvhd"); const long MOV_UDAT_ATOM = B2L_LONG("udat"); #if (RM_USE_MP4_SUBTITLE) // sub title const long MOV_MDAT_ATOM = B2L_LONG("mdat"); #endif typedef struct _MOV_HEADER { char dump[12]; // version(1) + flags(3) + creation time(4) + modification time(4) long time_scale; // 60000 long duration; // 1823985 } MOV_HEADER; MOVFormat::MOVFormat(RMfile in,VideoReadMode mode, VideoPreInfo* info) { _isValid = true; _file = in; _readMode = mode; _preInfo = info; #if (RM_MODEL != RM_MODEL_TYPE_MH9000 && !RM_MODEL_EMT_KR) _gps0Count = 0; #endif // _gsenCount = 0; #if (RM_MODEL == RM_MODEL_TYPE_NX_DRW22) _gps0 = 0; _zyx = 0; #elif (RM_MODEL == RM_MODEL_TYPE_ADT_CAPS || \ RM_MODEL == RM_MODEL_TYPE_XLDR_88 || \ RM_MODEL == RM_MODEL_TYPE_BV2000 || \ RM_MODEL == RM_MODEL_TYPE_KEIYO1 || \ RM_MODEL == RM_MODEL_TYPE_MBJ5010 || \ RM_MODEL == RM_MODEL_TYPE_FC_DR232W) _nmea = NULL; _sens = NULL; #elif (RM_MODEL_EMT_KR) _nmea = NULL; #endif // MODELS #if (RM_MODEL == RM_MODEL_TYPE_TB4000) _nmea = NULL; #endif if(_preInfo != NULL) { _preInfo->bDuration = false; _preInfo->duration = 0; #if (CHECK_VIDEO_BITRATE) _preInfo->movSize = 0; _preInfo->bMOVSize = false; #endif } _isValid = false; _file = in; if(_readMode == VideoReadDuration) { parse_duration(); } #if (CHECK_VIDEO_BITRATE) #if !defined(BBEXTRACT) else if (_readMode == AVIReadMOVSize) { parse_bitrate(); } #endif // BBEXTRACT #endif // CHECK_VIDEO_BITRATE else if (_readMode == VideoReadSensor) { parse_sensor(); } } bool MOVFormat::duration(RMfile in, VideoPreInfo* info) { MOVFormat mov = MOVFormat(in,VideoReadDuration,info); return mov.isValid(); } #if (CHECK_VIDEO_BITRATE) #if !defined(BBEXTRACT) bool MOVFormat::movSize(RMfile in, VideoPreInfo* info) { MOVFormat mov = MOVFormat(in,AVIReadMOVSize,info); return mov.isValid(); } #endif // #if !defined(BBEXTRACT) #endif // CHECK_VIDEO_BITRATE MOVFormat::~MOVFormat() { #if (RM_MODEL == RM_MODEL_TYPE_NX_DRW22) if(_gps0 != NULL) { free(_gps0); } if(_zyx != NULL) { free(_zyx); } #elif(RM_MODEL == RM_MODEL_TYPE_ADT_CAPS ||\ RM_MODEL == RM_MODEL_TYPE_XLDR_88 ||\ RM_MODEL == RM_MODEL_TYPE_BV2000 || \ RM_MODEL == RM_MODEL_TYPE_KEIYO1 ||\ RM_MODEL == RM_MODEL_TYPE_MBJ5010 || \ RM_MODEL == RM_MODEL_TYPE_FC_DR232W) if(_nmea != NULL) { free(_nmea); _nmea = NULL; } if(_sens != NULL) { free(_sens); } #elif (RM_MODEL == RM_MODEL_TYPE_TB4000) if(_nmea != NULL) { free(_nmea); _nmea = NULL; } #endif } // TAG Parser void MOVFormat::parse_all() { // 탐색 시작 RMfseek(_file,0,SEEK_END); parse(0,RMftell(_file)); } bool MOVFormat::parse_duration() { if(_preInfo == NULL) { return false; } init_parser(); set_tags("moov","mvhd",NULL); parse_all(); if(_stop_parse == false) { return false; } long mvhd_offset = _tag_offset_list[1]; RMfseek(_file,mvhd_offset+8,SEEK_SET); //long mvhd_size = _tag_size_list[1]; MOV_HEADER header = {0,}; RMfread(&header,sizeof(_MOV_HEADER),1,_file); _preInfo->duration = (unsigned int)((double)(SWAP_4BYTE(header.duration)) * 1000 / (double)SWAP_4BYTE(header.time_scale)); _preInfo->bDuration = true; _isValid = true; return true; } #if !defined(BBEXTRACT) void MOVFormat::parse_avc1(long offset, long size) { Q_UNUSED(size) // AVC Decoder Configuration Record RMfseek(_file,offset+32,SEEK_SET); unsigned char width_height_buffer[5] = {0,}; RMfread(width_height_buffer,4,1,_file); _preInfo->width = (int)rm_byte16_to_long(&width_height_buffer[0]); _preInfo->height = (int)rm_byte16_to_long(&width_height_buffer[2]); // qInfo() << "WH:" << _preInfo->width << _preInfo->height; } #if (CHECK_VIDEO_BITRATE) bool MOVFormat::parse_bitrate() { //RMfseek(_file,0,SEEK_END); if(_preInfo == NULL) { return false; } if(_preInfo->bDuration == false) { parse_duration(); } init_parser(); // 1. moov->trak->mdia->minf->stbl->stsd->avc1 순서대로 탐색 // https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25691 set_tags("moov","trak","mdia","minf","stbl","stsd","avc1",NULL); // 탐색 시작 parse_all(); if(_stop_parse == false) { return false; } // AVC1 TAG (MODEL: CS_92WQH) long avc1_offset = _tag_offset_list[_tag_count-1]; long avc1_size = _tag_size_list[_tag_count-1]; // AVC1 확인 (width/height) if(avc1_offset != 0 && avc1_size > 8) { parse_avc1(avc1_offset,avc1_size); } long stbl_offset = _tag_offset_list[4]; long stbl_size = _tag_size_list[4]; // 'stco' Chunk Offsets Box - Sample To Chunk Box init_parser(); set_tags("stbl","stsz",NULL); parse(stbl_offset,stbl_size,0); if(_stop_parse == false) { qInfo() << "stbl stsz search error!" << __FUNCTION__; return false; } long stsz_offset = _tag_offset_list[1]; //long stsz_size = _tag_size_list[1]; // size + type + version:1 + flags:3 + sample size:4 + number of entry: 4 + .... RMfseek(_file,stsz_offset+8+4+4,SEEK_SET); unsigned char long_buffer[5] = {0,}; RMfread(long_buffer,4,1,_file); long numFrames = rm_byte_to_long(long_buffer); if(numFrames == 0) { _preInfo->bMOVSize = false; _preInfo->movSize = 0; return false; } unsigned char* size_buffer = (unsigned char*)malloc(numFrames * 4); RMfread(size_buffer,4,numFrames,_file); long totalSize = 0; for(int i=0;ibMOVSize = true; _preInfo->movSize = (unsigned int)totalSize; return true; } #endif // #if (CHECK_VIDEO_BITRATE) #endif // #if !defined(BBEXTRACT) void MOVFormat::parse(unsigned int offset, unsigned int length, int depth) { if(_stop_parse == true || _tag_count <= depth) { return; } unsigned int off = offset; unsigned int stop_at = offset + length; while (off < stop_at) { if(_stop_parse == true) { return; } RMfseek(_file,off,SEEK_SET); // 1. ATOM 크기 확인 (unsigned 를 사용해야함) unsigned char atom_size_buffer[5] = {0,}; unsigned char atom_type_buffer[5] = {0,}; RMfread(atom_size_buffer,4,1,_file); long atom_size = rm_byte_to_long(atom_size_buffer); // apple 문서상 atom 의 최소 크기는 8임 // eg. model 92 ..mp4 - mdat tag // extended size field // 1, which means that the actual size is given in the extended size field, // an optional 64-bit field that follows the type field. // This accommodates media data atoms that contain more than 2^32 bytes. // type 뒤에 64 비트로 크기 지정 (extended size field) bool extendedSize = false; if(atom_size == 1) { extendedSize = true; } // 0, which is allowed only for a top-level atom, // designates the last atom in the file and indicates // that the atom extends to the end of the file. if(atom_size == 0) { RMfread(atom_type_buffer,4,1,_file); //#if !defined(BBEXTRACT) // qInfo() << "ZERO ATOM:" << (char*) atom_type_buffer; //#endif return; } if (RMftell(_file) == stop_at) { break; } // 2. ATOM 타입 확인 RMfread(atom_type_buffer,4,1,_file); long atom_type = rm_byte_to_long(atom_type_buffer); if(extendedSize) { unsigned char atom_extended_size_buffer[9] = {0,}; RMfread(atom_extended_size_buffer,8,1,_file); atom_size = rm_byte64_to_long(atom_extended_size_buffer); } long add_offset = 0; // "stsd" 의 경우 뒤에 추가 8 BYTE 존재 (Version:1,Flags:3,Number of entries:4) if(atom_type == rm_byte_to_long((unsigned char*)"stsd")) { add_offset = 8; } // qInfo() << "###" << (char*)atom_type_buffer; // 3. 지정된 ATOM 일 경우 내부탐색 (recursive) if (atom_type == _tag_list[depth]) { // if(_readMode == VideoReadSensor) // { // qInfo() << (char*)atom_type_buffer << "OFFSET:" << off << "SIZE:" << atom_size; // } //#if(DEBUG_MOV_STBL) //qInfo() << "tag:" << (char*)atom_type_buffer << " depth:" << depth << " offset:" << off; //#endif // 각 tag 의 offset, size 저장 _tag_offset_list[depth] = off; _tag_size_list[depth] = atom_size; #if (RM_MODEL == RM_MODEL_TYPE_TB4000) if((depth+1) == _tag_count && check_tag_value(atom_type,off,atom_size)) #else if((depth+1) == _tag_count && check_tag_value(atom_type,offset,atom_size)) #endif { _stop_parse = true; return; } // 하위 tag 탐색 parse(RMftell(_file)+add_offset,atom_size,depth+1); } off += atom_size; } } bool MOVFormat::check_tag_value(long atom_type, unsigned int offset, long size) { if(strlen(_tag_search_value) == 0) { return true; } #if (RM_MODEL == RM_MODEL_TYPE_TB4000 || RM_MODEL == RM_MODEL_TYPE_MH9000) // hdlr 타입 확인 if(atom_type == rm_byte_to_long((unsigned char*)"hdlr")) { const long data_offset = 8; fseek(_file,offset+data_offset,SEEK_SET); unsigned char tag_value[RM_MOV_MAX_TAG_VALUE_SIZE] = {0,}; #if (RM_MODEL == RM_MODEL_TYPE_MH9000) const long read_size = RM_MOV_MAX_TAG_VALUE_SIZE; fread(tag_value,read_size,1,_file); void* found = memmem(tag_value,RM_MOV_MAX_TAG_VALUE_SIZE,_tag_search_value,strlen(_tag_search_value)); #else // RM_MODEL_TYPE_TB4000 const long read_size = MIN(RM_MOV_MAX_TAG_VALUE_SIZE,size-data_offset); fread(tag_value,read_size,1,_file); void* found = memmem(tag_value,size-data_offset,_tag_search_value,strlen(_tag_search_value)); #endif // RM_MODEL_TYPE_TB4000 return (found != NULL); } #else // // STSD 타입 확인 (video, audio, subtitle) if(atom_type == rm_byte_to_long((unsigned char*)"stsd")) { // https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-61112 // 사양과는 다른것 같음 // size(4) + type(4) + version(3) + flag(1) + entry count(4) + Sample description size (4) const long data_offset = 20; fseek(_file,offset + data_offset,SEEK_SET); unsigned char tag_value[RM_MOV_MAX_TAG_VALUE_SIZE] = {0,}; long read_size = MIN(RM_MOV_MAX_TAG_VALUE_SIZE,size-data_offset); fread(tag_value,read_size,1,_file); return (memcmp(_tag_search_value,tag_value,strlen(_tag_search_value)) == 0); } #endif // SStarMeta return false; } void MOVFormat::set_tags(const char* tag,...) { init_parser(); va_list args; // initialize valist for num number of arguments va_start(args, tag); const char* each_tag = tag; int index = 0; while (each_tag != NULL) { // qInfo() << each_tag; _tag_list[index] = rm_byte_to_long((unsigned char*)each_tag); index++; each_tag = va_arg(args, const char*); } _tag_count = index; // clean memory reserved for valist va_end(args); } #if (RM_MODEL == 1) // NEXTEC bool MOVFormat::parse_sensor() { init_parser(); // 1. gseo // user 데이터 확인 완료 set_tags("gseo",NULL); // 탐색 시작 parse_all(); if(_stop_parse == false) { return false; } long gseo_offset = _tag_offset_list[0]; long gseo_size = _tag_size_list[0]; _gsenCount = (gseo_size - 8) / 6; if(_gsenCount > 0) { //qInfo() << __FUNCTION__ << QString().sprintf("GSEO OFFSET:%X",gseo_offset+15); RMfseek(_file,gseo_offset+15,SEEK_SET); _zyx = (int16_t*)malloc(6 * _gsenCount ); RMfread(_zyx,6,_gsenCount,_file); } // for(int i=0;i<_gsenCount;i++) { // qInfo() << "X" << _zyx[i*3+2] << "Y" << _zyx[i*3+1] << "Z" << _zyx[i*3+0]; // } // 1682 // 1. gpsa // user 데이터 확인 완료 set_tags("gpsa",NULL); // 탐색 시작 parse_all(); if(_stop_parse == false) { return false; } long gpsa_offset = _tag_offset_list[0]; long gpsa_size = _tag_size_list[0]; //qInfo() << gpsa_offset << gpsa_size; RMfseek(_file,gpsa_offset+gpsa_size,SEEK_SET); unsigned char csize[5] = {0,}; RMfread(csize,4,1,_file); _gps0Count = (B2L_LONG(csize) - 8) / 32; if(_gps0Count > 0) { unsigned char gps0[5] = {0,}; RMfread(gps0,4,1,_file); if(B2L_LONG(gps0) != B2L_LONG("gps0")) { return false; } _gps0 = (GPS0*)malloc(sizeof(_GPS0) * _gps0Count ); RMfread(_gps0,32,_gps0Count,_file); //qInfo() << "test:" << gpsCount << _gps0[gpsCount-1].lon; } // for(int i=0;i<_gps0Count;i++) { // qInfo() << "LAT" << _gps0[i].lat << "LON:" << _gps0[i].lon; // } return true; } #elif (RM_MODEL == RM_MODEL_TYPE_BV2000 ||\ RM_MODEL == RM_MODEL_TYPE_KEIYO1 ||\ RM_MODEL == RM_MODEL_TYPE_MBJ5010 ||\ RM_MODEL == RM_MODEL_TYPE_FC_DR232W) void MOVFormat::process_subtitle(const char* subtitle) { //qInfo() << subtitle; NMEA_INFO* nmea = (NMEA_INFO*)_nmea; NMEA_INFO* nmea_cur = &nmea[_gps0Count]; float* sensor = (float*)_sens; float* sensor_cur = &sensor[_gsenCount * 3]; QString s = QString(subtitle); QStringList ls = s.split(","); if(_sensorFPS <= 0) { _sensorFPS = ls.size() - 1; // 벤츠2 //qInfo() << "SENSOR FPS:" << _sensorFPS << __FUNCTION__; } //if(is10FPS == false && ls.size() != ((SENSOR_INTERVAL * SENSOR_FPS) + 1)) if(ls.size() != 41 && ls.size() != 11) { //qInfo() << _sensorFPS; //qInfo() << "!!!!!!!!!!!!!!!:" << s << ls.size() << "!=41,11" << LOG_FL; return; } for(int i=0;i<(_sensorFPS+1);i++) { QString line = ls.at(i); QStringList is = line.split("/"); if(is.size() != 8) { // 00:02:04 시간값 //qInfo() << "ERROR:" << is.size() << "!=8" << line << LOG_FL; continue; } // GPS if(i == 0 || i == _sensorFPS) { //qInfo() << _gps0Count << is; nmea_cur->Longitude = is.at(2).toFloat(); nmea_cur->Latitude = is.at(3).toFloat(); nmea_cur->nStatus = is.at(1) == "1" ? 1 : 0; nmea_cur->Speed = is.at(4).toInt(); nmea_cur++; _gps0Count++; } // SENSOR bool bs = false; // float x = (float)((int8_t)(is.at(6).toUInt(&bs,16)) << 24 >> 24); // float y = (float)((int8_t)(is.at(7).toUInt(&bs,16)) << 24 >> 24); // float z = (float)((int8_t)(is.at(5).toUInt(&bs,16)) << 24 >> 24); //uint nHex = sValue.toUInt(&bStatus,16); float x = ((float)(is.at(6).toInt(&bs,16)));// Y->X float y = ((float)(is.at(7).toInt(&bs,16)));// Z->Y float z = ((float)(is.at(5).toInt(&bs,16)));// X->Z #if (RM_MODEL == RM_MODEL_TYPE_MBJ5010 || RM_MODEL == RM_MODEL_TYPE_KEIYO1 || RM_MODEL == RM_MODEL_TYPE_FC_DR232W) if(_sensorFPS < 40) { // 요청은 X<->Y 교체였으나 실제로는 ZX 교체 // 상하(녹색):Y, 전후(노랑):Z, 좌우(빨강):X //qInfo() << "XY SWAP:" << __FUNCTION__ << __LINE__; float temp = x; x = z; z = temp; } #endif if(x > 0x1F) { x -= 0x100; } if(y > 0x1F) { y -= 0x100; } if(z > 0x1F) { z -= 0x100; } sensor_cur[(i*3)+0] = x / 16.0f; // X sensor_cur[(i*3)+1] = y / 16.0f; // Y sensor_cur[(i*3)+2] = z / 16.0f; // Z //qInfo() <trak->mdia->stbl을 확인하여, size 와 offset 이 0 이 아닌 경우 unsigned int stbl_offset = _tag_offset_list[4]; long stbl_size = _tag_size_list[4]; if(stbl_offset != 0 && stbl_size != 0) { // 3. 다시 parser 를 초기화 하고 stsz (각 자막별 크기) 확인 // https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25715 set_tags("stbl","stsz",NULL); // stbl 부터 탐색 시작 parse(stbl_offset,stbl_size); // 탐색된 stsz 에 subtitle의 size 들이 포함되어 있음 unsigned int stsz_offset = _tag_offset_list[1]; long stsz_size = _tag_size_list[1]; // 사이즈가 존재할 경우 if(stsz_offset != 0 && stsz_size != 0) { // 자막 개수 확인 long count = 0; long sample_size = 0; // 자막별 크기 (전체 동일한 경우과 개별 크기가 별도로 저장된 경우가 있음) unsigned char *sample_size_list = NULL; // 자막별 옵셋 (모두 별도로 저장되어 있음) unsigned char *sample_offset_list = NULL; // size(4) + type(4) + version/flag(4) + Sample size (모든 샘플 크기가 동일할 경우) + Number of entry(4) // 개수를 확인 RMfseek(_file,stsz_offset+12,SEEK_SET); sample_size = rm_read_number(_file); count = rm_read_number(_file); // 0 개 이상일 경우 또는 sample size 가 0 보다 클 경우 if(count > 0 || sample_size > 0) { int maxCount = (count + 1); // 2초 간격으로 2초씩 저장됨 _nmea = malloc(sizeof(_NMEA_INFO) * maxCount); _sens = malloc((sizeof(float) * 3) * maxCount * MAX_SENSOR_FPS); // 자막 레코드 별로 사이즈 별도 처리 하는 경우 if(sample_size == 0) { // 각 자막 크기 저장 공간 할당 + 읽어 오기 sample_size_list = (unsigned char*)malloc(4 * count); RMfread(sample_size_list,4 * count,1,_file); } // 다시 (moov->trak->mdia->)stbl->stco() 를 확인하여 각 자막별 옵셋 확인 // https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25715 set_tags("stbl","stco",NULL); // stbl 부터 탐색 시작 parse(stbl_offset,stbl_size); // stco 에 subtitle의 offset 들이 포함되어 있음 unsigned int stco_offset = _tag_offset_list[1]; long stco_size = _tag_size_list[1]; if(stco_offset != 0 && stco_size != 0) { // size(4) + type(4) + version/flag(4) + Entry Number + .... RMfseek(_file,stco_offset+12,SEEK_SET); // 개수 확인 long offset_count = rm_read_number(_file); // 개별 사이즈가 있는 경우 최소 개수 확인 if(sample_size == 0) { count = MIN(count,offset_count); } // 없는 경우 offset 개수가 자막 개수 else { count = offset_count; } // 옵셋 정보 할당 + 읽어오기 sample_offset_list = (unsigned char*)malloc(4 * count); RMfread(sample_offset_list,4 * count,1,_file); // 옵셋 + 크기로 자막 읽기 for(int i=0;i 256) { break; } char rmc[1024] = {0,}; RMfread(&rmc,dsize,1,_file); if(FMParseGPS::ParseRMC(rmc,&nmea[_gps0Count],strlen(rmc))) { _gps0Count += 1; } doffset += (8 + dsize); } if(_gps0Count > 0) { _nmea = malloc(sizeof(_NMEA_INFO) * _gps0Count); memcpy(_nmea,nmea,sizeof(_NMEA_INFO)*_gps0Count); } free(nmea); //qInfo() << QString().sprintf("%X",RMftell(_file)); unsigned char gsen_size_char[5] = {0,}; RMfread(&gsen_size_char,4,1,_file); uint32_t gsen_size = B2L_LONG(gsen_size_char); _gsenCount = gsen_size / 24; //qInfo() << QString().sprintf("%d (%d)",gsen_size,_gsenCount); unsigned char sens[5] = {0,}; RMfread(sens,4,1,_file); if(B2L_LONG(sens) != B2L_LONG("SENS")) { qInfo() << "NO SENSOR" << __FUNCTION__; } _sens = (SEN*)malloc(sizeof(_SEN) * _gsenCount); RMfread(_sens,sizeof(_SEN),_gsenCount,_file); return true; } #elif (RM_MODEL == RM_MODEL_TYPE_XLDR_88) /* bool MOVFormat::parse_sensor() { init_parser(); // 1. udat // user 데이터 확인 완료 set_tags("udat",NULL); // 탐색 시작 parse_all(); if(_stop_parse == false) { return false; } unsigned int udat_offset = _tag_offset_list[0]; long udat_size = _tag_size_list[0]; if(udat_offset != 0 && udat_size != 0) { RMfseek(_file,udat_offset+8,SEEK_SET); uint8_t *buffer = (uint8_t*)malloc(udat_size-8); RMfread(buffer,udat_size-8,1,_file); return true; } return false; } */ #elif (RM_MODEL == RM_MODEL_TYPE_TB4000) bool MOVFormat::parse_sensor() { init_parser(); /* TRACK 중 hdrl 의 내용에 SStarMeta 가 존재하는 TRACK 확인하여 // 2023/11/30 현재 시간값은 모두 0 이라고함 처리 */ // set_tags("moov","trak","mdia","hdlr",NULL); memcpy(_tag_search_value,"SStarMeta",strlen("SStarMeta")); // 탐색 시작 parse_all(); if(_stop_parse == false) { return false; } // TRAK (root) 확인 long trak_offset = _tag_offset_list[1]; long trak_size = _tag_size_list[1]; // ENSZ init_parser(); set_tags("trak","mdia","minf","stbl","ensz",NULL); // 암호화된 stsz => ensz 확인 parse(trak_offset,trak_size,0); if(_stop_parse == false) { return false; } long ensz_offset = _tag_offset_list[4]; long ensz_size = _tag_size_list[4] - 8; // REMOVE TAG+SIZE unsigned char* ensz = (unsigned char*)malloc(ensz_size); unsigned char* ensz_decrypted = (unsigned char*)malloc(ensz_size); memset(ensz,0,ensz_size); memset(ensz_decrypted,0,ensz_size); RMfseek(_file,ensz_offset+8,SEEK_SET); // TAG+SIZE RMfread(ensz,ensz_size,1,_file); QString password = RMApp::password(); // 암호화 해제 tb_decrypt((unsigned char*)password.toLatin1().data(), password.length(),ensz,ensz_size,ensz_decrypted); // QString b; // for(int i=0;i 10000 || count < 0) { return false; } // ENCO init_parser(); set_tags("trak","mdia","minf","stbl","enco",NULL); parse(trak_offset,trak_size,0); if(_stop_parse == false) { return false; } long enco_offset = _tag_offset_list[4]; long enco_size = _tag_size_list[4] - 8; unsigned char* enco = (unsigned char*)malloc(enco_size); unsigned char* enco_decrypted = (unsigned char*)malloc(enco_size); RMfseek(_file,enco_offset+8,SEEK_SET); RMfread(enco,enco_size,1,_file); tb_decrypt((unsigned char*)password.toLatin1().data(), password.length(),enco,enco_size,enco_decrypted); free(enco); _nmea = (GPSINFOCHUCK_TELEBIT*)malloc(sizeof(_GPSINFOCHUCK_TELEBIT) * count ); // 센서 데이터 읽기 for(int i=0;idwLat << item->dwLon << QString().sprintf("%.6f",item->dwLon) << __FUNCTION__; } free(enco_decrypted); _gps0Count = count; return false; } #elif (RM_MODEL == RM_MODEL_TYPE_MH9000) bool MOVFormat::parse_sensor() { init_parser(); // set_tags("moov","trak","mdia","hdlr",NULL); memcpy(_tag_search_value,"SStarMeta",strlen("SStarMeta")); // 탐색 시작 parse_all(); if(_stop_parse == false) { return false; } // TRAK (root) 확인 long trak_offset = _tag_offset_list[1]; long trak_size = _tag_size_list[1]; // ENSZ init_parser(); set_tags("trak","mdia","minf","stbl","stco",NULL); parse(trak_offset,trak_size,0); if(_stop_parse == false) { return false; } long stco_offset = _tag_offset_list[4]; long stco_size = _tag_size_list[4] - 8; // REMOVE TAG+SIZE RMfseek(_file,stco_offset+8+4,SEEK_SET); unsigned char long_buffer[5] = {0,}; RMfread(long_buffer,4,1,_file); long numFrames = rm_byte_to_long(long_buffer); // 256 - 8 = 248 //qInfo() << "stco_offset:" << stco_offset << "stco_size" << stco_size << "numFrames:" << numFrames << __FUNCTION__; // size + type + version:1 + flags:3 + sample size:4 + number of entry: 4 + .... //RMfseek(_file,stco_offset+8+4+4,SEEK_SET); // 2. SIZE OFFSET 읽기 unsigned char* offset_buffer = (unsigned char*)malloc(numFrames * 4); RMfread(offset_buffer,4,numFrames,_file); // 개수 확인 //long offset_count = rm_read_number(_file); // unsigned char* stco = (unsigned char*)malloc(stco_size); // memset(stco,0,stco_size); // RMfseek(_file,stco_offset+8,SEEK_SET); // TAG+SIZE // RMfread(stco,stco_size,1,_file); // long count = rm_byte_to_long((const unsigned char*)&stco[8]); _sens = (gps_chunk_t*)malloc(sizeof(_gps_chunk_t) * numFrames ); // 센서 데이터 읽기 for(int i=0;ispeed << QString().sprintf("%X",offset); //qInfo() << "i:" << i << item->year << item->mon << item->mday << __FUNCTION__; } free(offset_buffer); _gsenCount = numFrames; return false; } #elif (RM_MODEL_EMT_KR) bool MOVFormat::parse_sensor() { init_parser(); // mov.set_tags("moov","trak","mdia","minf","stbl","stsd",NULL); _gsenCount = 0; init_parser(); set_tags("moov","trak","mdia","minf","stbl","stsd",NULL); memcpy(_tag_search_value,"text",strlen("text")); parse_all(); if(_stop_parse == false) { return false; } // 2. 자막 trak 탐색 종료 후 탐색된 moov->trak->mdia->stbl을 확인하여, size 와 offset 이 0 이 아닌 경우 unsigned int stbl_offset = _tag_offset_list[4]; long stbl_size = _tag_size_list[4]; if(stbl_offset != 0 && stbl_size != 0) { // 3. 다시 parser 를 초기화 하고 stsz (각 자막별 크기) 확인 // https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25715 set_tags("stbl","stsz",NULL); // stbl 부터 탐색 시작 parse(stbl_offset,stbl_size); // 탐색된 stsz 에 subtitle의 size 들이 포함되어 있음 unsigned int stsz_offset = _tag_offset_list[1]; long stsz_size = _tag_size_list[1]; // 사이즈가 존재할 경우 if(stsz_offset != 0 && stsz_size != 0) { // 자막 개수 확인 long count = 0; long sample_size = 0; // 자막별 크기 (전체 동일한 경우과 개별 크기가 별도로 저장된 경우가 있음) unsigned char *sample_size_list = NULL; // 자막별 옵셋 (모두 별도로 저장되어 있음) unsigned char *sample_offset_list = NULL; // size(4) + type(4) + version/flag(4) + Sample size (모든 샘플 크기가 동일할 경우) + Number of entry(4) // 개수를 확인 RMfseek(_file,stsz_offset+12,SEEK_SET); sample_size = rm_read_number(_file); count = rm_read_number(_file); // 0 개 이상일 경우 또는 sample size 가 0 보다 클 경우 if(count > 0 || sample_size > 0) { int maxCount = (count + 1); // 2초 간격으로 2초씩 저장됨 _nmea = malloc(sizeof(_NMEA_INFO) * maxCount); //_sens = malloc((sizeof(float) * 3) * maxCount); // * MAX_SENSOR_FPS // 자막 레코드 별로 사이즈 별도 처리 하는 경우 if(sample_size == 0) { // 각 자막 크기 저장 공간 할당 + 읽어 오기 sample_size_list = (unsigned char*)malloc(4 * count); RMfread(sample_size_list,4 * count,1,_file); } // 다시 (moov->trak->mdia->)stbl->stco() 를 확인하여 각 자막별 옵셋 확인 // https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25715 set_tags("stbl","stco",NULL); // stbl 부터 탐색 시작 parse(stbl_offset,stbl_size); // stco 에 subtitle의 offset 들이 포함되어 있음 unsigned int stco_offset = _tag_offset_list[1]; long stco_size = _tag_size_list[1]; if(stco_offset != 0 && stco_size != 0) { // size(4) + type(4) + version/flag(4) + Entry Number + .... RMfseek(_file,stco_offset+12,SEEK_SET); // 개수 확인 long offset_count = rm_read_number(_file); // 개별 사이즈가 있는 경우 최소 개수 확인 if(sample_size == 0) { count = MIN(count,offset_count); } // 없는 경우 offset 개수가 자막 개수 else { count = offset_count; } // 옵셋 정보 할당 + 읽어오기 sample_offset_list = (unsigned char*)malloc(4 * count); RMfread(sample_offset_list,4 * count,1,_file); // 옵셋 + 크기로 자막 읽기 for(int i=0;i[^:]*):(?P\\d{4}-\\d{2}-\\d{2})\\s{0,2}(?P\\d{2}:\\d{2}:\\d{2}).*X:\\s{0,2}(?P[\\-0-9.]*).*Y:\\s{0,2}(?P[\\-0-9.]*).*Z:\\s{0,2}(?P[\\-0-9.]*)\\s{0,2}(?P[-0-9.]*)T\\s{0,2}(?P[\\-0-9.]*)V\\s{0,2}(?P[G|-])\\s{0,2}(?P[\\-0-9.]*)\\s{0,2}[N|-]\\s{0,2}(?P[\\-0-9.]*)\\s[E|-]\\s{0,2}(?P[\\-0-9.]*)km(.+E:(?P\\d{3}).+M:(?P\\d{3}))?",QRegularExpression::CaseInsensitiveOption); #else // USE_TRIGGER static QRegularExpression rx("(?P[^:]*):(?P\\d{4}-\\d{2}-\\d{2})\\s{0,2}(?P\\d{2}:\\d{2}:\\d{2}).*X:\\s{0,2}(?P[\\-0-9.]*).*Y:\\s{0,2}(?P[\\-0-9.]*).*Z:\\s{0,2}(?P[\\-0-9.]*)\\s{0,2}(?P[0-9.]*)T\\s{0,2}(?P[\\-0-9.]*)V\\s{0,2}(?P[G|-])\\s{0,2}(?P[\\-0-9.]*)\\s{0,2}[N|-]\\s{0,2}(?P[\\-0-9.]*)\\s[E|-]\\s{0,2}(?P[\\-0-9.]*)km",QRegularExpression::CaseInsensitiveOption); #endif // USE_TRIGGER QRegularExpressionMatch match = rx.match(subtitle); if (!match.isValid()) { return; } #if (RM_MODEL_EMT_KR) if(modelName.isEmpty()) { modelName = match.captured("model"); } #endif // #if (RM_MODEL_EMT_KR) //QString c0 = match.captured("ymd"); QString dateString = match.captured("ymd") + "_" + match.captured("hms"); QDateTime dt = QDateTime::fromString(dateString,"yyyy-MM-dd_HH:mm:ss"); dt.addSecs(9 * 3600); // 9시간 추가 if(!dt.isValid()) { qInfo() << dateString << subtitle << __FUNCTION__; } NMEA_INFO* nmea = (NMEA_INFO*)_nmea; NMEA_INFO* nmea_cur = &nmea[_gsenCount]; nmea_cur->nYear = dt.date().year(); nmea_cur->nMonth = dt.date().month(); nmea_cur->nDay = dt.date().day(); nmea_cur->nHour = dt.time().hour(); nmea_cur->nMin = dt.time().minute(); nmea_cur->nSec = dt.time().second(); bool bSuccess; nmea_cur->x = match.captured("x").toFloat(&bSuccess); nmea_cur->y = match.captured("y").toFloat(&bSuccess); nmea_cur->z = match.captured("z").toFloat(&bSuccess); //qInfo() << "x:" << match.captured("x") << "y:" << match.captured("y") << "z:" << match.captured("z") << __FUNCTION__; nmea_cur->Temperature = match.captured("t").toFloat(&bSuccess); nmea_cur->Voltage = match.captured("v").toFloat(&bSuccess); #if (USE_TRIGGER) bool bt; nmea_cur->eTrigger = match.captured("E").toInt(&bt); if(!bt) { nmea_cur->eTrigger = 255; } nmea_cur->mTrigger = match.captured("M").toInt(&bt); if(!bt) { nmea_cur->mTrigger = 255; } #endif // USE_TRIGGER bool bSuccess2; nmea_cur->Longitude = match.captured("lon").toDouble(&bSuccess2); nmea_cur->Latitude = match.captured("lat").toDouble(&bSuccess2); nmea_cur->Speed = match.captured("speed").toDouble(&bSuccess2); nmea_cur->nStatus = (nmea_cur->Longitude > 121.0f) && (nmea_cur->Longitude < 150.0f) && (nmea_cur->Latitude > 25.0f) && (nmea_cur->Latitude < 40.0f); if(!bSuccess) { // NM5000:2025-05-28 13:56:27 X: 0.00 Y: 0.00 Z: 0.00 012.0T 12.5V - --.------ - ---.------ - ---.-km/h E:255 V:903 S:36419k,0 //qInfo() << "ERROR:" << subtitle << __FUNCTION__; } _gsenCount += 1; } // EMT_KR #else // DUMMY bool MOVFormat::parse_sensor() { return false; } #endif // MODEL /* bool MOVFormat::parse_sensor() { init_parser(); // 1. moov->trak->mdia->minf->stbl->stsd->gpmd 순서대로 탐색 // user 데이터 확인 완료 set_tags("moov","trak","mdia","minf","stbl","stsd","gpmd",NULL); // 탐색 시작 parse_all(); if(_stop_parse == false) { return false; } // STBL (root) 확인 long stbl_offset = _tag_offset_list[4]; long stbl_size = _tag_size_list[4]; // 'stco' Chunk Offsets Box - Sample To Chunk Box init_parser(); set_tags("stbl","stsz",NULL); parse(stbl_offset,stbl_size,0); if(_stop_parse == false) { #if !defined(BBEXTRACT) qInfo() << "stbl stsz search error!" << __FUNCTION__; #endif return false; } long stsz_offset = _tag_offset_list[1]; // size + type + version:1 + flags:3 + sample size:4 + number of entry: 4 + .... RMfseek(_file,stsz_offset+8+4+4,SEEK_SET); unsigned char long_buffer[5] = {0,}; RMfread(long_buffer,4,1,_file); long numFrames = rm_byte_to_long(long_buffer); if(numFrames == 0) { return false; } _gpsenCount = numFrames; // 1. SIZE BUFFER 먼저 읽기 unsigned char* size_buffer = (unsigned char*)malloc(numFrames * 4); RMfread(size_buffer,4,numFrames,_file); init_parser(); set_tags("stbl","stco",NULL); parse(stbl_offset,stbl_size,0); if(_stop_parse == false) { #if !defined(BBEXTRACT) qInfo() << "stbl stco search error!" << __FUNCTION__; #endif return false; } long stco_offset = _tag_offset_list[1]; // size + type + version:1 + flags:3 + sample size:4 + number of entry: 4 + .... RMfseek(_file,stco_offset+8+4+4,SEEK_SET); // 2. SIZE OFFSET 읽기 unsigned char* offset_buffer = (unsigned char*)malloc(numFrames * 4); RMfread(offset_buffer,4,numFrames,_file); _gpsen = (GPSNR*)malloc(sizeof(_GPSNR) * numFrames ); #if !defined(BBEXTRACT) //qInfo() << "SIZE OF _GPSNR:" << sizeof(_GPSNR); #endif // 센서 데이터 읽기 for(int i=0;iidx << "GPS:" << item->byYear << item->byMon << item->byDay << item->byHour << item->byMin << item->bySec; #endif //qInfo() << i << ":" << rm_byte_to_long(&offset_buffer[i*4]) << rm_byte_to_long(&size_buffer[i*4]); //qInfo() << item->byYear << item->byMon << item->byDay << item->byHour << item->byMin << item->bySec << item->dwLat << item->dwLon; } //qInfo() << "SIZE _GPSNR: " << sizeof(_GPSNR); // 해제 free(offset_buffer); free(size_buffer); //free(_gpsen); return true; } */ void MOVFormat::init_parser() { // 초기화 //_tag_search_value_exist = false; memset(_tag_search_value,0,RM_MOV_MAX_TAG_VALUE_SIZE); memset(_tag_list,0,sizeof(long) * RM_MOV_MAX_DEPTH); _tag_count = 0; memset(_tag_offset_list,0,sizeof(unsigned int) * RM_MOV_MAX_DEPTH); memset(_tag_size_list,0,sizeof(long) * RM_MOV_MAX_DEPTH); _stop_parse = false; } #endif // #if (FILE_FORMAT_MOV)