diff options
Diffstat (limited to 'src/VBox/Main/src-client/EbmlWriter.cpp')
-rw-r--r-- | src/VBox/Main/src-client/EbmlWriter.cpp | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/src/VBox/Main/src-client/EbmlWriter.cpp b/src/VBox/Main/src-client/EbmlWriter.cpp new file mode 100644 index 00000000..552e9d36 --- /dev/null +++ b/src/VBox/Main/src-client/EbmlWriter.cpp @@ -0,0 +1,504 @@ +/* $Id: EbmlWriter.cpp $ */ +/** @file + * EbmlWriter.cpp - EBML writer + WebM container + */ + +/* + * Copyright (C) 2013 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/* + * This code is based on: + * + * Copyright (c) 2010 The WebM project authors. All Rights Reserved. + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "EbmlWriter.h" +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <VBox/log.h> + +static int ebml_Write(EbmlGlobal *glob, const void *pv, size_t cb) +{ + return RTFileWrite(glob->file, pv, cb, NULL); +} + +static int ebml_WriteU8(EbmlGlobal *glob, uint8_t u8) +{ + return ebml_Write(glob, &u8, 1); +} + +static int ebml_WriteU16(EbmlGlobal *glob, uint16_t u16) +{ + return ebml_Write(glob, &u16, 2); +} + +static int ebml_WriteU32(EbmlGlobal *glob, uint32_t u32) +{ + return ebml_Write(glob, &u32, 4); +} + +static int ebml_WriteU64(EbmlGlobal *glob, uint64_t u64) +{ + return ebml_Write(glob, &u64, 8); +} + +static int ebml_Serialize(EbmlGlobal *glob, const uint8_t *pb, size_t cb) +{ + for (; cb; cb--) + { + int rc = ebml_WriteU8(glob, pb[cb-1]); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + +static int ebml_WriteID(EbmlGlobal *glob, uint32_t class_id) +{ + int rc; + if (class_id >= 0x01000000) + rc = ebml_WriteU32(glob, RT_H2BE_U32(class_id)); + else if (class_id >= 0x00010000) + rc = ebml_Serialize(glob, (uint8_t*)&class_id, 3); + else if (class_id >= 0x00000100) + rc = ebml_WriteU16(glob, RT_H2BE_U16((uint16_t)class_id)); + else + rc = ebml_WriteU8(glob, (uint8_t)class_id); + return rc; +} + +static int ebml_SerializeUnsigned32(EbmlGlobal *glob, uint32_t class_id, uint32_t ui) +{ + int rc = ebml_WriteID(glob, class_id); + rc = ebml_WriteU8(glob, 4 | 0x80); + if (RT_SUCCESS(rc)) + rc = ebml_WriteU32(glob, RT_H2BE_U32(ui)); + return rc; +} + +static int ebml_StartSubElement(EbmlGlobal *glob, uint64_t *ebmlLoc, uint32_t class_id) +{ + // todo this is always taking 8 bytes, this may need later optimization + // this is a key that says lenght unknown + uint64_t unknownLen = UINT64_C(0x01FFFFFFFFFFFFFF); + + ebml_WriteID(glob, class_id); + *ebmlLoc = RTFileTell(glob->file); + return ebml_WriteU64(glob, RT_H2BE_U64(unknownLen)); +} + +static int ebml_EndSubElement(EbmlGlobal *glob, uint64_t ebmlLoc) +{ + /* Save the current file pointer */ + uint64_t pos = RTFileTell(glob->file); + + /* Calculate the size of this element */ + uint64_t size = pos - ebmlLoc - 8; + size |= UINT64_C(0x0100000000000000); + + /* Seek back to the beginning of the element and write the new size */ + RTFileSeek(glob->file, ebmlLoc, RTFILE_SEEK_BEGIN, NULL); + int rc = ebml_WriteU64(glob, RT_H2BE_U64(size)); + + /* Reset the file pointer */ + RTFileSeek(glob->file, pos, RTFILE_SEEK_BEGIN, NULL); + + return rc; +} + +static int ebml_WriteLen(EbmlGlobal *glob, uint64_t val) +{ + //TODO check and make sure we are not > than 0x0100000000000000LLU + size_t size = 8; + uint64_t minVal = UINT64_C(0x00000000000000ff); //mask to compare for byte size + + for (size = 1; size < 8; size ++) + { + if (val < minVal) + break; + + minVal = (minVal << 7); + } + + val |= (UINT64_C(0x000000000000080) << ((size - 1) * 7)); + + return ebml_Serialize(glob, (uint8_t *)&val, size); +} + +static int ebml_WriteString(EbmlGlobal *glob, const char *str) +{ + const size_t cb = strlen(str); + int rc = ebml_WriteLen(glob, cb); + //TODO: it's not clear from the spec whether the nul terminator + //should be serialized too. For now we omit the null terminator. + if (RT_SUCCESS(rc)) + rc = ebml_Write(glob, str, cb); + return rc; +} + +int Ebml_SerializeUnsigned64(EbmlGlobal *glob, uint32_t class_id, uint64_t ui) +{ + int rc = ebml_WriteID(glob, class_id); + if (RT_SUCCESS(rc)) + rc = ebml_WriteU8(glob, 8 | 0x80); + if (RT_SUCCESS(rc)) + rc = ebml_WriteU64(glob, RT_H2BE_U64(ui)); + return rc; +} + +int Ebml_SerializeUnsigned(EbmlGlobal *glob, uint32_t class_id, uint32_t ui) +{ + int rc = ebml_WriteID(glob, class_id); + if (RT_FAILURE(rc)) + return rc; + + uint32_t minVal = 0x7fLU; //mask to compare for byte size + size_t size = 8; //size in bytes to output + for (size = 1; size < 4; size ++) + { + if (ui < minVal) + break; + + minVal <<= 7; + } + + rc = ebml_WriteU8(glob, 0x80 | size); + if (RT_SUCCESS(rc)) + rc = ebml_Serialize(glob, (uint8_t*)&ui, size); + return rc; +} + +//TODO: perhaps this is a poor name for this id serializer helper function +int Ebml_SerializeBinary(EbmlGlobal *glob, uint32_t class_id, uint32_t bin) +{ + int size; + for (size = 4; size > 1; size--) + { + if (bin & 0x000000ff << ((size-1) * 8)) + break; + } + int rc = ebml_WriteID(glob, class_id); + if (RT_SUCCESS(rc)) + rc = ebml_WriteLen(glob, size); + if (RT_SUCCESS(rc)) + rc = ebml_WriteID(glob, bin); + return rc; +} + +int Ebml_SerializeFloat(EbmlGlobal *glob, uint32_t class_id, double d) +{ + int rc = ebml_WriteID(glob, class_id); + if (RT_SUCCESS(rc)) + rc = ebml_WriteU8(glob, 0x80 | 8); + if (RT_SUCCESS(rc)) + rc = ebml_WriteU64(glob, RT_H2BE_U64(*(uint64_t*)&d)); + return rc; +} + +int Ebml_SerializeString(EbmlGlobal *glob, uint32_t class_id, const char *s) +{ + int rc = ebml_WriteID(glob, class_id); + if (RT_SUCCESS(rc)) + rc = ebml_WriteString(glob, s); + return rc; +} + +int Ebml_WriteWebMSeekElement(EbmlGlobal *ebml, uint32_t id, uint64_t pos) +{ + uint64_t offset = pos - ebml->position_reference; + uint64_t start; + int rc = ebml_StartSubElement(ebml, &start, Seek); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeBinary(ebml, SeekID, id); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned64(ebml, SeekPosition, offset); + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(ebml, start); + return rc; +} + +int Ebml_WriteWebMSeekInfo(EbmlGlobal *ebml) +{ + int rc = VINF_SUCCESS; + + /* Save the current file pointer */ + uint64_t pos = RTFileTell(ebml->file); + + if (ebml->seek_info_pos) + rc = RTFileSeek(ebml->file, ebml->seek_info_pos, RTFILE_SEEK_BEGIN, NULL); + else + ebml->seek_info_pos = pos; + + { + uint64_t start; + + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(ebml, &start, SeekHead); + if (RT_SUCCESS(rc)) + rc = Ebml_WriteWebMSeekElement(ebml, Tracks, ebml->track_pos); + if (RT_SUCCESS(rc)) + rc = Ebml_WriteWebMSeekElement(ebml, Cues, ebml->cue_pos); + if (RT_SUCCESS(rc)) + rc = Ebml_WriteWebMSeekElement(ebml, Info, ebml->segment_info_pos); + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(ebml, start); + } + { + //segment info + uint64_t startInfo; + uint64_t frame_time; + + frame_time = (uint64_t)1000 * ebml->framerate.den / ebml->framerate.num; + ebml->segment_info_pos = RTFileTell(ebml->file); + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(ebml, &startInfo, Info); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(ebml, TimecodeScale, 1000000); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeFloat(ebml, Segment_Duration, + (double)(ebml->last_pts_ms + frame_time)); + char szVersion[64]; + RTStrPrintf(szVersion, sizeof(szVersion), "vpxenc%", + ebml->debug ? vpx_codec_version_str() : ""); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeString(ebml, MuxingApp, szVersion); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeString(ebml, WritingApp, szVersion); + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(ebml, startInfo); + } + return rc; +} + +int Ebml_WriteWebMFileHeader(EbmlGlobal *glob, + const vpx_codec_enc_cfg_t *cfg, + const struct vpx_rational *fps) +{ + int rc = VINF_SUCCESS; + { + uint64_t start; + rc = ebml_StartSubElement(glob, &start, EBML); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, EBMLVersion, 1); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, EBMLReadVersion, 1); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, EBMLMaxIDLength, 4); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, EBMLMaxSizeLength, 8); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeString(glob, DocType, "webm"); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, DocTypeVersion, 2); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, DocTypeReadVersion, 2); + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(glob, start); + } + { + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(glob, &glob->startSegment, Segment); + glob->position_reference = RTFileTell(glob->file); + glob->framerate = *fps; + if (RT_SUCCESS(rc)) + rc = Ebml_WriteWebMSeekInfo(glob); + { + uint64_t trackStart; + glob->track_pos = RTFileTell(glob->file); + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(glob, &trackStart, Tracks); + { + uint32_t trackNumber = 1; + uint32_t trackID = 0; + + uint64_t start; + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(glob, &start, TrackEntry); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, TrackNumber, trackNumber); + glob->track_id_pos = RTFileTell(glob->file); + ebml_SerializeUnsigned32(glob, TrackUID, trackID); + Ebml_SerializeUnsigned(glob, TrackType, 1); //video is always 1 + Ebml_SerializeString(glob, CodecID, "V_VP8"); + { + uint64_t videoStart; + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(glob, &videoStart, Video); + uint32_t pixelWidth = cfg->g_w; + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, PixelWidth, pixelWidth); + uint32_t pixelHeight = cfg->g_h; + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, PixelHeight, pixelHeight); + double frameRate = (double)fps->num / (double)fps->den; + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeFloat(glob, FrameRate, frameRate); + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(glob, videoStart); + } + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(glob, start); //Track Entry + } + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(glob, trackStart); + } + // segment element is open + } + return rc; +} + +int Ebml_WriteWebMBlock(EbmlGlobal *glob, + const vpx_codec_enc_cfg_t *cfg, + const vpx_codec_cx_pkt_t *pkt) +{ + uint16_t block_timecode = 0; + int64_t pts_ms; + int start_cluster = 0; + int rc = VINF_SUCCESS; + + /* Calculate the PTS of this frame in milliseconds */ + pts_ms = pkt->data.frame.pts * 1000 + * (uint64_t)cfg->g_timebase.num / (uint64_t)cfg->g_timebase.den; + if (pts_ms <= glob->last_pts_ms) + pts_ms = glob->last_pts_ms + 1; + glob->last_pts_ms = pts_ms; + + /* Calculate the relative time of this block */ + if (pts_ms - glob->cluster_timecode > 65536) + start_cluster = 1; + else + block_timecode = (uint16_t)(pts_ms - glob->cluster_timecode); + + int fKeyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY); + if (start_cluster || fKeyframe) + { + if (glob->cluster_open) + rc = ebml_EndSubElement(glob, glob->startCluster); + + /* Open the new cluster */ + block_timecode = 0; + glob->cluster_open = 1; + glob->cluster_timecode = (uint32_t)pts_ms; + glob->cluster_pos = RTFileTell(glob->file); + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(glob, &glob->startCluster, Cluster); //cluster + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, Timecode, glob->cluster_timecode); + + /* Save a cue point if this is a keyframe. */ + if (fKeyframe) + { + struct cue_entry *cue; + + glob->cue_list = (cue_entry*)RTMemRealloc(glob->cue_list, (glob->cues+1) * sizeof(cue_entry)); + cue = &glob->cue_list[glob->cues]; + cue->time = glob->cluster_timecode; + cue->loc = glob->cluster_pos; + glob->cues++; + } + } + + /* Write the Simple Block */ + if (RT_SUCCESS(rc)) + rc = ebml_WriteID(glob, SimpleBlock); + + uint32_t block_length = pkt->data.frame.sz + 4; + block_length |= 0x10000000; + if (RT_SUCCESS(rc)) + rc = ebml_WriteU32(glob, RT_H2BE_U32(block_length)); + + uint8_t track_number = 0x80 | 1; + if (RT_SUCCESS(rc)) + rc = ebml_WriteU8(glob, track_number); + + if (RT_SUCCESS(rc)) + rc = ebml_WriteU16(glob, RT_H2BE_U16(block_timecode)); + + uint8_t flags = 0; + if (fKeyframe) + flags |= 0x80; + if (pkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE) + flags |= 0x08; + if (RT_SUCCESS(rc)) + rc = ebml_WriteU8(glob, flags); + + if (RT_SUCCESS(rc)) + rc = ebml_Write(glob, pkt->data.frame.buf, pkt->data.frame.sz); + + return rc; +} + +int Ebml_WriteWebMFileFooter(EbmlGlobal *glob, long hash) +{ + int rc = VINF_SUCCESS; + if (glob->cluster_open) + rc = ebml_EndSubElement(glob, glob->startCluster); + + { + uint64_t start; + + glob->cue_pos = RTFileTell(glob->file); + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(glob, &start, Cues); + for (unsigned i = 0; i < glob->cues; i++) + { + struct cue_entry *cue = &glob->cue_list[i]; + uint64_t startSub; + + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(glob, &startSub, CuePoint); + { + uint64_t startSubsub; + + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, CueTime, cue->time); + if (RT_SUCCESS(rc)) + rc = ebml_StartSubElement(glob, &startSubsub, CueTrackPositions); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned(glob, CueTrack, 1); + if (RT_SUCCESS(rc)) + rc = Ebml_SerializeUnsigned64(glob, CueClusterPosition, + cue->loc - glob->position_reference); + //Ebml_SerializeUnsigned(glob, CueBlockNumber, cue->blockNumber); + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(glob, startSubsub); + } + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(glob, startSub); + } + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(glob, start); + } + + if (RT_SUCCESS(rc)) + rc = ebml_EndSubElement(glob, glob->startSegment); + + /* Patch up the seek info block */ + if (RT_SUCCESS(rc)) + rc = Ebml_WriteWebMSeekInfo(glob); + + /* Patch up the track id */ + if (RT_SUCCESS(rc)) + rc = RTFileSeek(glob->file, glob->track_id_pos, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + rc = ebml_SerializeUnsigned32(glob, TrackUID, glob->debug ? 0xDEADBEEF : hash); + + if (RT_SUCCESS(rc)) + rc = RTFileSeek(glob->file, 0, RTFILE_SEEK_END, NULL); + return rc; +} |