// // TVMConverter.cpp // // Created by Jürg Müller on 20.07.2013 // // Copyright (C) 2014 Jürg Müller, CH-5524 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation version 3 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see http://www.gnu.org/licenses/ . // //////////////////////////////////////////////////////////////////////////////// // // "The Tube" (from equinux) generates streams called Media.tvm (in a packet // ending with ".ttrec"). TVMconverter is a programm which takes an MPEG-2 and // mp2 streams out of a tvm stream. // // Media.tvm --> Media.mpg and Media_x.mp2 (for x the audio track number) // (for composite or s-video input: outputs Media.wav) // // The video can be display by vlc 2.1.1, and the audio by iTunes or QuickTime // Player. // // // The TVM format is as follows: // - there are 3 different records starting with 0xab, 0xac, and 0xad // - the data record 0xac has 3 subtypes: the MPEG-2 record and two audio // record types (mp2 and wav) // // // Generate program with: // // g++ TVMConverter.cpp -o TVMConverter // // // Usage: // // ./TVMConverter [ [ "trace" ]] // // (default path to Media.tvm is "~/Movies") // // // Generate TS-stream by using ffmpeg: // // ffmpeg -f mpegvideo -fflags +genpts -i Media.mpg -i Media_0.mp2 -vcodec copy Media.TS // // // contact use: http://avidemux.org/smif/index.php/topic,12374.0.html // // // last change: 17.7.2014 / JM // //////////////////////////////////////////////////////////////////////////////// #include #include #include char Movies_Path[1024] = "~/Movies/"; /////////////////////////////////////////////////////////////////////////////// // // simple memory class // class TMemoryStream { protected: unsigned char * mMemory; unsigned long mMemorySize; // size of mMemory unsigned long mSize; // used size static const unsigned long MaxFileBuffer = 0x400000; // 4 MB bool mReadFromFile; FILE * mInp; unsigned long mByteReadFromFile; unsigned long mFileLength; unsigned long mBufferOffset; public: TMemoryStream(); ~TMemoryStream(); const unsigned char * GetMemory() const { return mMemory; }; const unsigned char * GetMemory(unsigned long Offset) const; unsigned long Size() const { if (mReadFromFile) return mFileLength; else return mSize; }; unsigned long MemorySize() const { return mMemorySize; }; unsigned char GetByte(unsigned long Offset) const; unsigned short GetWord(unsigned long Offset) const; unsigned GetUInt(unsigned long Offset) const; void Append(const unsigned char * Stream, unsigned long StreamLen); void SetSize(unsigned long NewSize); bool LoadFromFile(const char * FileName); bool LoadNextBlock(unsigned long Pos); bool SaveToFile(const char * FileName) const; }; TMemoryStream::TMemoryStream() { mMemory = NULL; mMemorySize = 0; mSize = 0; mReadFromFile = false; mInp = NULL; mByteReadFromFile = 0; mBufferOffset = 0; mFileLength = 0; } TMemoryStream::~TMemoryStream() { if (mInp) fclose(mInp); SetSize(0); } void TMemoryStream::SetSize(unsigned long NewSize) { mSize = NewSize; if (NewSize == 0) { if (mMemory) delete [] mMemory; mMemory = NULL; mMemorySize = 0; return; } if (NewSize <= mMemorySize) return; unsigned char * NewMemory; NewMemory = new unsigned char[NewSize]; memset(NewMemory, 0, NewSize); if (mMemory) { if (NewMemory) memcpy(NewMemory, mMemory, mMemorySize); delete [] mMemory; } mMemory = NewMemory; mMemorySize = NewSize; } void TMemoryStream::Append(const unsigned char * Stream, unsigned long StreamLen) { if (!StreamLen || !Stream) return; if (mMemorySize < StreamLen + mSize) { unsigned long Size = mSize; SetSize(Size + StreamLen + 2000); mSize = Size; } memmove(mMemory + mSize, Stream, StreamLen); mSize += StreamLen; } const unsigned char * TMemoryStream::GetMemory(unsigned long Offset) const { if (Offset < mBufferOffset) return NULL; Offset -= mBufferOffset; if (Offset + 1 <= mSize && mMemory) return mMemory + Offset; return NULL; } unsigned char TMemoryStream::GetByte(unsigned long Offset) const { if (Offset < mBufferOffset) return 0; Offset -= mBufferOffset; if (Offset + 1 <= mSize && mMemory) return mMemory[Offset]; return 0; } unsigned short TMemoryStream::GetWord(unsigned long Offset) const { // big endian return (GetByte(Offset) << 8) + GetByte(Offset+1); } unsigned TMemoryStream::GetUInt(unsigned long Offset) const { return (GetWord(Offset) << 16) + GetWord(Offset+2); } bool TMemoryStream::LoadFromFile(const char * FileName) { if (mInp) fclose(mInp); mReadFromFile = false; mInp = NULL; mByteReadFromFile = 0; mBufferOffset = 0; mFileLength = 0; SetSize(0); bool Ok; mInp = fopen(FileName, "rb"); Ok = mInp != NULL; if (mInp) { if (!fseek(mInp, 0, SEEK_END)) { mFileLength = ftell(mInp); if (fseek(mInp, 0, SEEK_SET)) mFileLength = 0; } if (mFileLength) { unsigned long Length = MaxFileBuffer; if (Length > mFileLength) Length = mFileLength; SetSize(Length); mSize = 0; mReadFromFile = true; Ok = LoadNextBlock(0); } else { fclose(mInp); } } return Ok && mReadFromFile; } bool TMemoryStream::LoadNextBlock(unsigned long Pos) { if (!mReadFromFile || !mInp || Pos < mBufferOffset) return false; if (mByteReadFromFile >= mFileLength) return true; Pos -= mBufferOffset; if ((3*mSize) / 4 > Pos) return true; unsigned long MoveBytes = mMemorySize / 2; if (mSize < MoveBytes) MoveBytes = mSize; if (MoveBytes && mSize > MoveBytes) memmove(mMemory, mMemory + MoveBytes, mSize - MoveBytes); mBufferOffset += MoveBytes; mSize -= MoveBytes; unsigned long Length = mFileLength - mByteReadFromFile; if (Length > mMemorySize - mSize) Length = mMemorySize - mSize; bool Ok = true; if (Length > 0) { Ok = fread(mMemory + mByteReadFromFile - mBufferOffset, 1, Length, mInp) == Length; mByteReadFromFile += Length; mSize += Length; } return Ok && mSize == mByteReadFromFile - mBufferOffset; } bool WriteFileStream(FILE * File, const void * Stream, unsigned long StreamLen) { if (!File || !Stream) return false; bool Ok = true; unsigned long WriteLen; const unsigned char * Ptr = (const unsigned char *) Stream; while (Ok && StreamLen) { WriteLen = StreamLen; if (WriteLen > 0x800000) // 8 MB WriteLen = 0x800000; Ok = fwrite(Ptr, 1, WriteLen, File) == WriteLen; Ptr += WriteLen; StreamLen -= WriteLen; } return Ok; } //////////////////////////////////////////////////////////////////////////////// // // structs for building wav file (PCM audio) // typedef unsigned int FOURCC; FOURCC MakeFour(const char * FourCC) { return *(FOURCC *)FourCC; } struct TFmtChunk { // offset 12 FOURCC Signature; // "fmt " header signature unsigned int Length; // length of TFmtChunk header // (16 Byte; without Signature and Length) unsigned short FormatTag; // sample data format unsigned short ChannelCount;// channels: 1 = mono, 2 = stereo unsigned int SampleRate; // (e.g. 44100 bits / sec) unsigned int BytesPerSec; // Sample-Rate * Block-Align unsigned short BlockAlign;// channels * bits/sample / 8 unsigned short BitsPerSample;// 8 or 16 void Init() { Signature = MakeFour("fmt "); Length = sizeof(TFmtChunk) - 8; FormatTag = 1; // PCM_WAVE; ChannelCount = 2; BitsPerSample = 16; SampleRate = 44100; BlockAlign = (ChannelCount * BitsPerSample) / 8; BytesPerSec = SampleRate * BlockAlign; } TFmtChunk() { Init(); } }; struct TDataChunk { // offset 36 FOURCC Signatur; // "data" header signature unsigned int Length; // length of TFmtChunk header void Init() { Signatur = MakeFour("data"); Length = 0; } TDataChunk() { Init(); } }; struct TChunkHeader { // offset 0 FOURCC RIFF; // "RIFF" unsigned int FileLength; // file length - 8 // chunk size - 8 FOURCC Typ; // "WAVE" oder "AVI " void Init() { RIFF = MakeFour("RIFF"); Typ = MakeFour("WAVE"); FileLength = sizeof(TFmtChunk) + sizeof(TDataChunk) + 4; } TChunkHeader() { Init(); } }; //////////////////////////////////////////////////////////////////////////////// void ChangeSeparator(char * Dir) { #if defined(__WINDOWS__) for (int i = 0; Dir[i]; i++) if (Dir[i] == '/') Dir[i] = '\\'; #else char * home = getenv("HOME"); if (home && Dir[0] == '~') { unsigned long len = strlen(home); memmove(Dir + len - 1, Dir, strlen(Dir) + 1); memmove(Dir, home, len); } #endif } int main(int argc, char* argv[]) { bool trace = false; TMemoryStream tvm; TMemoryStream wav; char Name[1024]; printf("\"Media.tvm\" converter\n" "copyright (c) 2015 Jürg Müller, CH-5524\n\n"); if (argc > 1) { strcpy(Movies_Path, argv[1]); if (strlen(Movies_Path) == 0 || Movies_Path[strlen(Movies_Path)-1] != '/') strcat(Movies_Path, "/"); } ChangeSeparator(Movies_Path); if (argc > 2) trace = !strcmp(argv[2], "trace"); strcpy(Name, Movies_Path); strcat(Name, "Media.tvm"); printf("\n"); if (!tvm.LoadFromFile(Name)) { printf("File '%s' not read!\n", Name); printf("\n\nUsage:"); printf("TVMConverter [ [ \"debug\" ]]\n\n"); return 1; } strcpy(Name, Movies_Path); strcat(Name, "Media.mpg"); FILE * video = fopen(Name, "wb+"); bool Ok = video; // parser position and record length unsigned long Pos = 0; unsigned long Len; unsigned char b = tvm.GetByte(Pos); unsigned Offset; const unsigned MaxTracks = 20; unsigned TracksUsed = 0; int ActualTrack = -1; unsigned Track[MaxTracks] = {0}; FILE * mp2[MaxTracks] = {NULL}; while (Ok && Pos + 0x10 < tvm.Size()) { Offset = 0; // just 3 record types 0xab, 0xac, and 0xad switch (b) { case 0xab: // 0xab36f312 Len = 0x23; if (trace) { printf("GMT: %2.2d:%2.2d:%2.2d %2.2d.%2.2d.%4.4d " "recording time: %d.%3.3d s\n", tvm.GetByte(Pos + 0x1c), tvm.GetByte(Pos + 0x1d), tvm.GetByte(Pos + 0x1e), tvm.GetByte(Pos + 0x1b), tvm.GetByte(Pos + 0x1a), tvm.GetByte(Pos + 0x19) + 1900, tvm.GetUInt(Pos + 0x1f) / 1000, tvm.GetUInt(Pos + 0x1f) % 1000); for (int q = 0x19; q < 0x23; q++) printf("%2.2x ", tvm.GetByte(Pos + Offset + q)); printf("\n"); } break; case 0xac: // 0xacbd48fb data (video and audio) { unsigned format = tvm.GetByte(Pos + 0x1e); Len = tvm.GetUInt(Pos + 0x1a); Offset = 0x1f; // length of header switch (format) { case 0x00: Offset += 6; // PCM has an additional record header // of 6 bytes break; case 0x01: case 0x02: break; default: Ok = false; break; } if (Pos + Offset + Len < tvm.Size()) { switch (format) { case 0x00: // PCM wav.Append(tvm.GetMemory(Pos + Offset), Len); break; case 0x01: // mp2 { ActualTrack = -1; unsigned Tr = tvm.GetWord(Pos + Offset + 2); for (unsigned i = 0; i < TracksUsed; i++) if (Track[i] == Tr) { ActualTrack = i; break; } if (ActualTrack == -1 && TracksUsed < MaxTracks) { char Buff[1024]; ActualTrack = TracksUsed++; Track[ActualTrack] = Tr; sprintf(Buff, "%sMedia_%d.mp2", Movies_Path, ActualTrack); mp2[ActualTrack] = fopen(Buff, "wb+"); } if (ActualTrack >= 0) Ok = WriteFileStream(mp2[ActualTrack], tvm.GetMemory(Pos + Offset), Len); break; } case 0x02: // video Ok = WriteFileStream(video, tvm.GetMemory(Pos + Offset), Len); break; default: Ok = false; break; } } break; } case 0xad: // 0xad85f74e // ad 85 f7 4e 99 e8 6a 21 aa be 1f 54 (00 00 00 05 incrementing index) (b6 22 23 af only this 4 bytes varies) 01 00 00 Len = 0x17; if (trace) { for (int q = 0; q < 0x17; q++) printf("%2.2x ", tvm.GetByte(Pos + Offset + q)); printf("\n"); } break; default: Ok = false; break; } if (trace) printf("%8.8x: %8.8x %2.2x\n", (unsigned) Pos, (unsigned) Len, b); Pos += Len + Offset; b = tvm.GetByte(Pos); if (Ok && !tvm.LoadNextBlock(Pos)) Ok = false; } if (trace) printf("%8.8x size = %8.8x\n\n", (unsigned) Pos, (unsigned) tvm.Size()); printf("generated files:\n\n"); if (video) { fclose(video); printf("--- %s\n", Name); } for (unsigned i = 0; i < TracksUsed; i++) if (mp2[i]) { fclose(mp2[i]); printf("--- %sMedia_%d.mp2\n", Movies_Path, i); } if (wav.Size() > 0) { TChunkHeader Header; TFmtChunk Fmt; TDataChunk Data; Len = wav.Size(); Header.FileLength += Len; Data.Length += Len; strcpy(Name, Movies_Path); strcat(Name, "Media.wav"); FILE * wav_file = fopen(Name, "wb"); bool Ok = wav_file; if (Ok) { Ok = WriteFileStream(wav_file, &Header, sizeof(Header)); if (Ok) Ok = WriteFileStream(wav_file, &Fmt, sizeof(Fmt)); if (Ok) Ok = WriteFileStream(wav_file, &Data, sizeof(Data)); if (Ok) Ok = WriteFileStream(wav_file, wav.GetMemory(), Len); fclose(wav_file); printf("--- %s\n", Name); } } printf("\nOk: %s\n\n", Ok ? "true" : "false"); if (Ok) { printf("to get a TS-stream, use:\n\n ffmpeg -f mpegvideo -fflags +genpts -i Media.mpg"); if (wav.Size() > 0) printf(" -i Media.wav"); else { for (int i = 0; i < TracksUsed; i++) printf(" -i Media_%d.mp2", i); for (int i = 0; i <= TracksUsed; i++) printf(" -map %d:0", i); } printf(" -vcodec copy Media.TS"); } return Ok ? 0 : -1; }