| /* |
| * Copyright (c) 2013 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 "webmenc.h" |
| |
| #include <limits.h> |
| #include <string.h> |
| |
| #include "third_party/libmkv/EbmlWriter.h" |
| #include "third_party/libmkv/EbmlIDs.h" |
| |
| void Ebml_Write(struct EbmlGlobal *glob, |
| const void *buffer_in, |
| unsigned long len) { |
| (void) fwrite(buffer_in, 1, len, glob->stream); |
| } |
| |
| #define WRITE_BUFFER(s) \ |
| for (i = len - 1; i >= 0; i--) { \ |
| x = (char)(*(const s *)buffer_in >> (i * CHAR_BIT)); \ |
| Ebml_Write(glob, &x, 1); \ |
| } |
| |
| void Ebml_Serialize(struct EbmlGlobal *glob, |
| const void *buffer_in, |
| int buffer_size, |
| unsigned long len) { |
| char x; |
| int i; |
| |
| /* buffer_size: |
| * 1 - int8_t; |
| * 2 - int16_t; |
| * 3 - int32_t; |
| * 4 - int64_t; |
| */ |
| switch (buffer_size) { |
| case 1: |
| WRITE_BUFFER(int8_t) |
| break; |
| case 2: |
| WRITE_BUFFER(int16_t) |
| break; |
| case 4: |
| WRITE_BUFFER(int32_t) |
| break; |
| case 8: |
| WRITE_BUFFER(int64_t) |
| break; |
| default: |
| break; |
| } |
| } |
| #undef WRITE_BUFFER |
| |
| /* Need a fixed size serializer for the track ID. libmkv provides a 64 bit |
| * one, but not a 32 bit one. |
| */ |
| static void Ebml_SerializeUnsigned32(struct EbmlGlobal *glob, |
| unsigned int class_id, |
| uint64_t ui) { |
| const unsigned char sizeSerialized = 4 | 0x80; |
| Ebml_WriteID(glob, class_id); |
| Ebml_Serialize(glob, &sizeSerialized, sizeof(sizeSerialized), 1); |
| Ebml_Serialize(glob, &ui, sizeof(ui), 4); |
| } |
| |
| static void Ebml_StartSubElement(struct EbmlGlobal *glob, |
| EbmlLoc *ebmlLoc, |
| unsigned int class_id) { |
| const uint64_t kEbmlUnknownLength = LITERALU64(0x01FFFFFF, 0xFFFFFFFF); |
| Ebml_WriteID(glob, class_id); |
| *ebmlLoc = ftello(glob->stream); |
| Ebml_Serialize(glob, &kEbmlUnknownLength, sizeof(kEbmlUnknownLength), 8); |
| } |
| |
| static void Ebml_EndSubElement(struct EbmlGlobal *glob, EbmlLoc *ebmlLoc) { |
| off_t pos; |
| uint64_t size; |
| |
| /* Save the current stream pointer. */ |
| pos = ftello(glob->stream); |
| |
| /* Calculate the size of this element. */ |
| size = pos - *ebmlLoc - 8; |
| size |= LITERALU64(0x01000000, 0x00000000); |
| |
| /* Seek back to the beginning of the element and write the new size. */ |
| fseeko(glob->stream, *ebmlLoc, SEEK_SET); |
| Ebml_Serialize(glob, &size, sizeof(size), 8); |
| |
| /* Reset the stream pointer. */ |
| fseeko(glob->stream, pos, SEEK_SET); |
| } |
| |
| void write_webm_seek_element(struct EbmlGlobal *ebml, |
| unsigned int id, |
| off_t pos) { |
| uint64_t offset = pos - ebml->position_reference; |
| EbmlLoc start; |
| Ebml_StartSubElement(ebml, &start, Seek); |
| Ebml_SerializeBinary(ebml, SeekID, id); |
| Ebml_SerializeUnsigned64(ebml, SeekPosition, offset); |
| Ebml_EndSubElement(ebml, &start); |
| } |
| |
| void write_webm_seek_info(struct EbmlGlobal *ebml) { |
| off_t pos; |
| EbmlLoc start; |
| EbmlLoc startInfo; |
| uint64_t frame_time; |
| char version_string[64]; |
| |
| /* Save the current stream pointer. */ |
| pos = ftello(ebml->stream); |
| |
| if (ebml->seek_info_pos) |
| fseeko(ebml->stream, ebml->seek_info_pos, SEEK_SET); |
| else |
| ebml->seek_info_pos = pos; |
| |
| Ebml_StartSubElement(ebml, &start, SeekHead); |
| write_webm_seek_element(ebml, Tracks, ebml->track_pos); |
| write_webm_seek_element(ebml, Cues, ebml->cue_pos); |
| write_webm_seek_element(ebml, Info, ebml->segment_info_pos); |
| Ebml_EndSubElement(ebml, &start); |
| |
| /* Create and write the Segment Info. */ |
| if (ebml->debug) { |
| strcpy(version_string, "vpxenc"); |
| } else { |
| strcpy(version_string, "vpxenc "); |
| strncat(version_string, |
| vpx_codec_version_str(), |
| sizeof(version_string) - 1 - strlen(version_string)); |
| } |
| |
| frame_time = (uint64_t)1000 * ebml->framerate.den |
| / ebml->framerate.num; |
| ebml->segment_info_pos = ftello(ebml->stream); |
| Ebml_StartSubElement(ebml, &startInfo, Info); |
| Ebml_SerializeUnsigned(ebml, TimecodeScale, 1000000); |
| Ebml_SerializeFloat(ebml, Segment_Duration, |
| (double)(ebml->last_pts_ms + frame_time)); |
| Ebml_SerializeString(ebml, 0x4D80, version_string); |
| Ebml_SerializeString(ebml, 0x5741, version_string); |
| Ebml_EndSubElement(ebml, &startInfo); |
| } |
| |
| void write_webm_file_header(struct EbmlGlobal *glob, |
| const vpx_codec_enc_cfg_t *cfg, |
| const struct vpx_rational *fps, |
| stereo_format_t stereo_fmt, |
| unsigned int fourcc) { |
| EbmlLoc start; |
| EbmlLoc trackStart; |
| EbmlLoc videoStart; |
| unsigned int trackNumber = 1; |
| uint64_t trackID = 0; |
| unsigned int pixelWidth = cfg->g_w; |
| unsigned int pixelHeight = cfg->g_h; |
| |
| /* Write the EBML header. */ |
| Ebml_StartSubElement(glob, &start, EBML); |
| Ebml_SerializeUnsigned(glob, EBMLVersion, 1); |
| Ebml_SerializeUnsigned(glob, EBMLReadVersion, 1); |
| Ebml_SerializeUnsigned(glob, EBMLMaxIDLength, 4); |
| Ebml_SerializeUnsigned(glob, EBMLMaxSizeLength, 8); |
| Ebml_SerializeString(glob, DocType, "webm"); |
| Ebml_SerializeUnsigned(glob, DocTypeVersion, 2); |
| Ebml_SerializeUnsigned(glob, DocTypeReadVersion, 2); |
| Ebml_EndSubElement(glob, &start); |
| |
| /* Open and begin writing the segment element. */ |
| Ebml_StartSubElement(glob, &glob->startSegment, Segment); |
| glob->position_reference = ftello(glob->stream); |
| glob->framerate = *fps; |
| write_webm_seek_info(glob); |
| |
| /* Open and write the Tracks element. */ |
| glob->track_pos = ftello(glob->stream); |
| Ebml_StartSubElement(glob, &trackStart, Tracks); |
| |
| /* Open and write the Track entry. */ |
| Ebml_StartSubElement(glob, &start, TrackEntry); |
| Ebml_SerializeUnsigned(glob, TrackNumber, trackNumber); |
| glob->track_id_pos = ftello(glob->stream); |
| Ebml_SerializeUnsigned32(glob, TrackUID, trackID); |
| Ebml_SerializeUnsigned(glob, TrackType, 1); |
| Ebml_SerializeString(glob, CodecID, |
| fourcc == VP8_FOURCC ? "V_VP8" : "V_VP9"); |
| Ebml_StartSubElement(glob, &videoStart, Video); |
| Ebml_SerializeUnsigned(glob, PixelWidth, pixelWidth); |
| Ebml_SerializeUnsigned(glob, PixelHeight, pixelHeight); |
| Ebml_SerializeUnsigned(glob, StereoMode, stereo_fmt); |
| Ebml_EndSubElement(glob, &videoStart); |
| |
| /* Close Track entry. */ |
| Ebml_EndSubElement(glob, &start); |
| |
| /* Close Tracks element. */ |
| Ebml_EndSubElement(glob, &trackStart); |
| |
| /* Segment element remains open. */ |
| } |
| |
| void write_webm_block(struct EbmlGlobal *glob, |
| const vpx_codec_enc_cfg_t *cfg, |
| const vpx_codec_cx_pkt_t *pkt) { |
| unsigned int block_length; |
| unsigned char track_number; |
| uint16_t block_timecode = 0; |
| unsigned char flags; |
| int64_t pts_ms; |
| int start_cluster = 0, is_keyframe; |
| |
| /* 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 > SHRT_MAX) |
| start_cluster = 1; |
| else |
| block_timecode = (uint16_t)pts_ms - glob->cluster_timecode; |
| |
| is_keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY); |
| if (start_cluster || is_keyframe) { |
| if (glob->cluster_open) |
| 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 = ftello(glob->stream); |
| Ebml_StartSubElement(glob, &glob->startCluster, Cluster); |
| Ebml_SerializeUnsigned(glob, Timecode, glob->cluster_timecode); |
| |
| /* Save a cue point if this is a keyframe. */ |
| if (is_keyframe) { |
| struct cue_entry *cue, *new_cue_list; |
| |
| new_cue_list = realloc(glob->cue_list, |
| (glob->cues + 1) * sizeof(struct cue_entry)); |
| if (new_cue_list) |
| glob->cue_list = new_cue_list; |
| else |
| fatal("Failed to realloc cue list."); |
| |
| cue = &glob->cue_list[glob->cues]; |
| cue->time = glob->cluster_timecode; |
| cue->loc = glob->cluster_pos; |
| glob->cues++; |
| } |
| } |
| |
| /* Write the Simple Block. */ |
| Ebml_WriteID(glob, SimpleBlock); |
| |
| block_length = (unsigned int)pkt->data.frame.sz + 4; |
| block_length |= 0x10000000; |
| Ebml_Serialize(glob, &block_length, sizeof(block_length), 4); |
| |
| track_number = 1; |
| track_number |= 0x80; |
| Ebml_Write(glob, &track_number, 1); |
| |
| Ebml_Serialize(glob, &block_timecode, sizeof(block_timecode), 2); |
| |
| flags = 0; |
| if (is_keyframe) |
| flags |= 0x80; |
| if (pkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE) |
| flags |= 0x08; |
| Ebml_Write(glob, &flags, 1); |
| |
| Ebml_Write(glob, pkt->data.frame.buf, (unsigned int)pkt->data.frame.sz); |
| } |
| |
| void write_webm_file_footer(struct EbmlGlobal *glob, int hash) { |
| EbmlLoc start_cues; |
| EbmlLoc start_cue_point; |
| EbmlLoc start_cue_tracks; |
| unsigned int i; |
| |
| if (glob->cluster_open) |
| Ebml_EndSubElement(glob, &glob->startCluster); |
| |
| glob->cue_pos = ftello(glob->stream); |
| Ebml_StartSubElement(glob, &start_cues, Cues); |
| |
| for (i = 0; i < glob->cues; i++) { |
| struct cue_entry *cue = &glob->cue_list[i]; |
| Ebml_StartSubElement(glob, &start_cue_point, CuePoint); |
| Ebml_SerializeUnsigned(glob, CueTime, cue->time); |
| |
| Ebml_StartSubElement(glob, &start_cue_tracks, CueTrackPositions); |
| Ebml_SerializeUnsigned(glob, CueTrack, 1); |
| Ebml_SerializeUnsigned64(glob, CueClusterPosition, |
| cue->loc - glob->position_reference); |
| Ebml_EndSubElement(glob, &start_cue_tracks); |
| |
| Ebml_EndSubElement(glob, &start_cue_point); |
| } |
| |
| Ebml_EndSubElement(glob, &start_cues); |
| |
| /* Close the Segment. */ |
| Ebml_EndSubElement(glob, &glob->startSegment); |
| |
| /* Patch up the seek info block. */ |
| write_webm_seek_info(glob); |
| |
| /* Patch up the track id. */ |
| fseeko(glob->stream, glob->track_id_pos, SEEK_SET); |
| Ebml_SerializeUnsigned32(glob, TrackUID, glob->debug ? 0xDEADBEEF : hash); |
| |
| fseeko(glob->stream, 0, SEEK_END); |
| } |