From 03848f5ca744e10238d5bc3f62491ae5934a2371 Mon Sep 17 00:00:00 2001 From: Tom Finegan Date: Tue, 5 Nov 2013 10:02:18 -0800 Subject: [PATCH] Move WebM writing support out of vpxenc.c. This is mainly a clean up patchset. It moves the WebM writing support out of vpxenc and into its own source file. Changes to tools_common and vpxdec result from relocation of shared bits of code. Change-Id: Iee55d3285f56e0a548f791094fb14c5ac5346a26 --- examples.mk | 1 + tools_common.c | 31 +++- tools_common.h | 20 ++- vpxdec.c | 20 +-- vpxenc.c | 423 +------------------------------------------------ webmenc.c | 357 +++++++++++++++++++++++++++++++++++++++++ webmenc.h | 87 ++++++++++ 7 files changed, 503 insertions(+), 436 deletions(-) create mode 100644 webmenc.c create mode 100644 webmenc.h diff --git a/examples.mk b/examples.mk index 88327fed6..a236b4541 100644 --- a/examples.mk +++ b/examples.mk @@ -37,6 +37,7 @@ vpxdec.DESCRIPTION = Full featured decoder UTILS-$(CONFIG_ENCODERS) += vpxenc.c vpxenc.SRCS += args.c args.h y4minput.c y4minput.h vpxenc.SRCS += tools_common.c tools_common.h +vpxenc.SRCS += webmenc.c webmenc.h vpxenc.SRCS += vpx_ports/mem_ops.h vpxenc.SRCS += vpx_ports/mem_ops_aligned.h vpxenc.SRCS += vpx_ports/vpx_timer.h diff --git a/tools_common.c b/tools_common.c index 92de79418..44b2a3fa0 100644 --- a/tools_common.c +++ b/tools_common.c @@ -7,8 +7,11 @@ * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ -#include #include "tools_common.h" + +#include +#include + #if defined(_WIN32) || defined(__OS2__) #include #include @@ -20,6 +23,18 @@ #endif #endif +#define LOG_ERROR(label) do {\ + const char *l = label;\ + va_list ap;\ + va_start(ap, fmt);\ + if (l)\ + fprintf(stderr, "%s: ", l);\ + vfprintf(stderr, fmt, ap);\ + fprintf(stderr, "\n");\ + va_end(ap);\ +} while (0) + + FILE *set_binary_mode(FILE *stream) { (void)stream; #if defined(_WIN32) || defined(__OS2__) @@ -27,3 +42,17 @@ FILE *set_binary_mode(FILE *stream) { #endif return stream; } + +void die(const char *fmt, ...) { + LOG_ERROR(NULL); + usage_exit(); +} + +void fatal(const char *fmt, ...) { + LOG_ERROR("Fatal"); + exit(EXIT_FAILURE); +} + +void warn(const char *fmt, ...) { + LOG_ERROR("Warning"); +} diff --git a/tools_common.h b/tools_common.h index 9e56149a5..7dfd5ad21 100644 --- a/tools_common.h +++ b/tools_common.h @@ -7,10 +7,24 @@ * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ -#ifndef TOOLS_COMMON_H -#define TOOLS_COMMON_H +#ifndef TOOLS_COMMON_H_ +#define TOOLS_COMMON_H_ + +#include + +#define VP8_FOURCC (0x30385056) +#define VP9_FOURCC (0x30395056) +#define VP8_FOURCC_MASK (0x00385056) +#define VP9_FOURCC_MASK (0x00395056) /* Sets a stdio stream into binary mode */ FILE *set_binary_mode(FILE *stream); -#endif +void die(const char *fmt, ...); +void fatal(const char *fmt, ...); +void warn(const char *fmt, ...); + +/* The tool including this file must define usage_exit() */ +void usage_exit(); + +#endif // TOOLS_COMMON_H_ diff --git a/vpxdec.c b/vpxdec.c index 1860474cb..8e575e16f 100644 --- a/vpxdec.c +++ b/vpxdec.c @@ -50,8 +50,6 @@ static const char *exec_name; -#define VP8_FOURCC (0x00385056) -#define VP9_FOURCC (0x00395056) static const struct { char const *name; const vpx_codec_iface_t *(*iface)(void); @@ -59,10 +57,10 @@ static const struct { unsigned int fourcc_mask; } ifaces[] = { #if CONFIG_VP8_DECODER - {"vp8", vpx_codec_vp8_dx, VP8_FOURCC, 0x00FFFFFF}, + {"vp8", vpx_codec_vp8_dx, VP8_FOURCC_MASK, 0x00FFFFFF}, #endif #if CONFIG_VP9_DECODER - {"vp9", vpx_codec_vp9_dx, VP9_FOURCC, 0x00FFFFFF}, + {"vp9", vpx_codec_vp9_dx, VP9_FOURCC_MASK, 0x00FFFFFF}, #endif }; @@ -143,7 +141,7 @@ static const arg_def_t *vp8_pp_args[] = { }; #endif -static void usage_exit() { +void usage_exit() { int i; fprintf(stderr, "Usage: %s filename\n\n" @@ -178,14 +176,6 @@ static void usage_exit() { exit(EXIT_FAILURE); } -void die(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - usage_exit(); -} - static unsigned int mem_get_le16(const void *vmem) { unsigned int val; const unsigned char *mem = (const unsigned char *)vmem; @@ -575,9 +565,9 @@ file_is_webm(struct input_ctx *input, codec_id = nestegg_track_codec_id(input->nestegg_ctx, i); if (codec_id == NESTEGG_CODEC_VP8) { - *fourcc = VP8_FOURCC; + *fourcc = VP8_FOURCC_MASK; } else if (codec_id == NESTEGG_CODEC_VP9) { - *fourcc = VP9_FOURCC; + *fourcc = VP9_FOURCC_MASK; } else { fprintf(stderr, "Not VPx video, quitting.\n"); exit(1); diff --git a/vpxenc.c b/vpxenc.c index aa99c6b7c..df75b853a 100644 --- a/vpxenc.c +++ b/vpxenc.c @@ -34,6 +34,8 @@ #include #endif +#include "third_party/libyuv/include/libyuv/scale.h" + #if CONFIG_VP8_ENCODER || CONFIG_VP9_ENCODER #include "vpx/vp8cx.h" #endif @@ -44,10 +46,9 @@ #include "vpx_ports/mem_ops.h" #include "vpx_ports/vpx_timer.h" #include "tools_common.h" +#include "webmenc.h" #include "y4minput.h" -#include "third_party/libmkv/EbmlWriter.h" -#include "third_party/libmkv/EbmlIDs.h" -#include "third_party/libyuv/include/libyuv/scale.h" + /* Need special handling of these functions on Windows */ #if defined(_MSC_VER) @@ -89,8 +90,6 @@ static size_t wrap_fwrite(const void *ptr, size_t size, size_t nmemb, static const char *exec_name; -#define VP8_FOURCC (0x30385056) -#define VP9_FOURCC (0x30395056) static const struct codec_item { char const *name; const vpx_codec_iface_t *(*iface)(void); @@ -109,37 +108,6 @@ static const struct codec_item { #endif }; -static void usage_exit(); - -#define LOG_ERROR(label) do \ - {\ - const char *l=label;\ - va_list ap;\ - va_start(ap, fmt);\ - if(l)\ - fprintf(stderr, "%s: ", l);\ - vfprintf(stderr, fmt, ap);\ - fprintf(stderr, "\n");\ - va_end(ap);\ - } while(0) - -void die(const char *fmt, ...) { - LOG_ERROR(NULL); - usage_exit(); -} - - -void fatal(const char *fmt, ...) { - LOG_ERROR("Fatal"); - exit(EXIT_FAILURE); -} - - -void warn(const char *fmt, ...) { - LOG_ERROR("Warning"); -} - - static void warn_or_exit_on_errorv(vpx_codec_ctx_t *ctx, int fatal, const char *s, va_list ap) { if (ctx->err) { @@ -293,15 +261,6 @@ vpx_fixed_buf_t stats_get(stats_io_t *stats) { return stats->buf; } -/* Stereo 3D packed frame format */ -typedef enum stereo_format { - STEREO_FORMAT_MONO = 0, - STEREO_FORMAT_LEFT_RIGHT = 1, - STEREO_FORMAT_BOTTOM_TOP = 2, - STEREO_FORMAT_TOP_BOTTOM = 3, - STEREO_FORMAT_RIGHT_LEFT = 11 -} stereo_format_t; - enum video_file_type { FILE_TYPE_RAW, FILE_TYPE_IVF, @@ -496,376 +455,6 @@ static void write_ivf_frame_size(FILE *outfile, size_t size) { } -typedef off_t EbmlLoc; - - -struct cue_entry { - unsigned int time; - uint64_t loc; -}; - - -struct EbmlGlobal { - int debug; - - FILE *stream; - int64_t last_pts_ms; - vpx_rational_t framerate; - - /* These pointers are to the start of an element */ - off_t position_reference; - off_t seek_info_pos; - off_t segment_info_pos; - off_t track_pos; - off_t cue_pos; - off_t cluster_pos; - - /* This pointer is to a specific element to be serialized */ - off_t track_id_pos; - - /* These pointers are to the size field of the element */ - EbmlLoc startSegment; - EbmlLoc startCluster; - - uint32_t cluster_timecode; - int cluster_open; - - struct cue_entry *cue_list; - unsigned int cues; - -}; - - -void Ebml_Write(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(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(EbmlGlobal *glob, unsigned long class_id, uint64_t ui) { - 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(EbmlGlobal *glob, EbmlLoc *ebmlLoc, - unsigned long class_id) { - /* todo this is always taking 8 bytes, this may need later optimization */ - /* this is a key that says length unknown */ - uint64_t unknownLen = LITERALU64(0x01FFFFFF, 0xFFFFFFFF); - - Ebml_WriteID(glob, class_id); - *ebmlLoc = ftello(glob->stream); - Ebml_Serialize(glob, &unknownLen, sizeof(unknownLen), 8); -} - -static void -Ebml_EndSubElement(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); -} - - -static void -write_webm_seek_element(EbmlGlobal *ebml, unsigned long 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); -} - - -static void -write_webm_seek_info(EbmlGlobal *ebml) { - - off_t pos; - - /* 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; - - { - EbmlLoc start; - - 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); - } - { - /* segment info */ - EbmlLoc startInfo; - uint64_t frame_time; - char version_string[64]; - - /* Assemble version string */ - 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); - } -} - - -static void -write_webm_file_header(EbmlGlobal *glob, - const vpx_codec_enc_cfg_t *cfg, - const struct vpx_rational *fps, - stereo_format_t stereo_fmt, - unsigned int fourcc) { - { - EbmlLoc start; - 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); - } - { - Ebml_StartSubElement(glob, &glob->startSegment, Segment); - glob->position_reference = ftello(glob->stream); - glob->framerate = *fps; - write_webm_seek_info(glob); - - { - EbmlLoc trackStart; - glob->track_pos = ftello(glob->stream); - Ebml_StartSubElement(glob, &trackStart, Tracks); - { - unsigned int trackNumber = 1; - uint64_t trackID = 0; - - EbmlLoc start; - 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"); - { - unsigned int pixelWidth = cfg->g_w; - unsigned int pixelHeight = cfg->g_h; - - EbmlLoc videoStart; - 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); - } - Ebml_EndSubElement(glob, &start); /* Track Entry */ - } - Ebml_EndSubElement(glob, &trackStart); - } - /* segment element is open */ - } -} - - -static void -write_webm_block(EbmlGlobal *glob, - const vpx_codec_enc_cfg_t *cfg, - const vpx_codec_cx_pkt_t *pkt) { - unsigned long block_length; - unsigned char track_number; - unsigned short 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 = (unsigned short)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); /* 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 long)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 long)pkt->data.frame.sz); -} - - -static void -write_webm_file_footer(EbmlGlobal *glob, long hash) { - - if (glob->cluster_open) - Ebml_EndSubElement(glob, &glob->startCluster); - - { - EbmlLoc start; - unsigned int i; - - glob->cue_pos = ftello(glob->stream); - Ebml_StartSubElement(glob, &start, Cues); - for (i = 0; i < glob->cues; i++) { - struct cue_entry *cue = &glob->cue_list[i]; - EbmlLoc start; - - Ebml_StartSubElement(glob, &start, CuePoint); - { - EbmlLoc start; - - Ebml_SerializeUnsigned(glob, CueTime, cue->time); - - Ebml_StartSubElement(glob, &start, CueTrackPositions); - Ebml_SerializeUnsigned(glob, CueTrack, 1); - Ebml_SerializeUnsigned64(glob, CueClusterPosition, - cue->loc - glob->position_reference); - Ebml_EndSubElement(glob, &start); - } - Ebml_EndSubElement(glob, &start); - } - Ebml_EndSubElement(glob, &start); - } - - 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); -} - /* Murmur hash derived from public domain reference implementation at * http:// sites.google.com/site/murmurhash/ @@ -1172,7 +761,7 @@ static const int vp9_arg_ctrl_map[] = { static const arg_def_t *no_args[] = { NULL }; -static void usage_exit() { +void usage_exit() { int i; fprintf(stderr, "Usage: %s -o dst_filename src_filename \n", @@ -1647,7 +1236,7 @@ struct stream_state { struct stream_config config; FILE *file; struct rate_hist rate_hist; - EbmlGlobal ebml; + struct EbmlGlobal ebml; uint32_t hash; uint64_t psnr_sse_total; uint64_t psnr_samples_total; diff --git a/webmenc.c b/webmenc.c new file mode 100644 index 000000000..777b2aee6 --- /dev/null +++ b/webmenc.c @@ -0,0 +1,357 @@ +/* + * 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 +#include + +#include "third_party/libmkv/EbmlWriter.h" +#include "third_party/libmkv/EbmlIDs.h" + +#if defined(_MSC_VER) +/* MSVS uses _f{seek,tell}i64 */ +#define fseeko _fseeki64 +#define ftello _ftelli64 +#elif defined(_WIN32) +/* MinGW defines off_t as long + and uses f{seek,tell}o64/off64_t for large files */ +#define fseeko fseeko64 +#define ftello ftello64 +#define off_t off64_t +#endif + +#define LITERALU64(hi, lo) ((((uint64_t)hi) << 32) | lo) + +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) { + 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) { + /* todo this is always taking 8 bytes, this may need later optimization */ + /* this is a key that says length unknown */ + uint64_t unknownLen = LITERALU64(0x01FFFFFF, 0xFFFFFFFF); + + Ebml_WriteID(glob, class_id); + *ebmlLoc = ftello(glob->stream); + Ebml_Serialize(glob, &unknownLen, sizeof(unknownLen), 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; + + /* 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; + + { + EbmlLoc start; + + 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); + } + { + /* segment info */ + EbmlLoc startInfo; + uint64_t frame_time; + char version_string[64]; + + /* Assemble version string */ + 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; + 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); + } + { + Ebml_StartSubElement(glob, &glob->startSegment, Segment); + glob->position_reference = ftello(glob->stream); + glob->framerate = *fps; + write_webm_seek_info(glob); + + { + EbmlLoc trackStart; + glob->track_pos = ftello(glob->stream); + Ebml_StartSubElement(glob, &trackStart, Tracks); + { + unsigned int trackNumber = 1; + uint64_t trackID = 0; + + EbmlLoc start; + 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"); + { + unsigned int pixelWidth = cfg->g_w; + unsigned int pixelHeight = cfg->g_h; + + EbmlLoc videoStart; + 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); + } + Ebml_EndSubElement(glob, &start); /* Track Entry */ + } + Ebml_EndSubElement(glob, &trackStart); + } + /* segment element is 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); /* 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) { + if (glob->cluster_open) + Ebml_EndSubElement(glob, &glob->startCluster); + + { + EbmlLoc start; + unsigned int i; + + glob->cue_pos = ftello(glob->stream); + Ebml_StartSubElement(glob, &start, Cues); + for (i = 0; i < glob->cues; i++) { + struct cue_entry *cue = &glob->cue_list[i]; + EbmlLoc start; + + Ebml_StartSubElement(glob, &start, CuePoint); + { + EbmlLoc start; + + Ebml_SerializeUnsigned(glob, CueTime, cue->time); + + Ebml_StartSubElement(glob, &start, CueTrackPositions); + Ebml_SerializeUnsigned(glob, CueTrack, 1); + Ebml_SerializeUnsigned64(glob, CueClusterPosition, + cue->loc - glob->position_reference); + Ebml_EndSubElement(glob, &start); + } + Ebml_EndSubElement(glob, &start); + } + Ebml_EndSubElement(glob, &start); + } + + 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); +} diff --git a/webmenc.h b/webmenc.h new file mode 100644 index 000000000..d5efd9b5e --- /dev/null +++ b/webmenc.h @@ -0,0 +1,87 @@ +/* + * 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. + */ +#ifndef WEBMENC_H_ +#define WEBMENC_H_ + +#include +#include + +#if defined(_MSC_VER) +/* MSVS doesn't define off_t */ +typedef __int64 off_t; +#else +#include +#endif + +#include "tools_common.h" +#include "vpx/vpx_encoder.h" + +typedef off_t EbmlLoc; + +struct cue_entry { + unsigned int time; + uint64_t loc; +}; + +struct EbmlGlobal { + int debug; + + FILE *stream; + int64_t last_pts_ms; + vpx_rational_t framerate; + + /* These pointers are to the start of an element */ + off_t position_reference; + off_t seek_info_pos; + off_t segment_info_pos; + off_t track_pos; + off_t cue_pos; + off_t cluster_pos; + + /* This pointer is to a specific element to be serialized */ + off_t track_id_pos; + + /* These pointers are to the size field of the element */ + EbmlLoc startSegment; + EbmlLoc startCluster; + + uint32_t cluster_timecode; + int cluster_open; + + struct cue_entry *cue_list; + unsigned int cues; +}; + +/* Stereo 3D packed frame format */ +typedef enum stereo_format { + STEREO_FORMAT_MONO = 0, + STEREO_FORMAT_LEFT_RIGHT = 1, + STEREO_FORMAT_BOTTOM_TOP = 2, + STEREO_FORMAT_TOP_BOTTOM = 3, + STEREO_FORMAT_RIGHT_LEFT = 11 +} stereo_format_t; + +void write_webm_seek_element(struct EbmlGlobal *ebml, + unsigned int id, + off_t pos); + +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); + +void write_webm_block(struct EbmlGlobal *glob, + const vpx_codec_enc_cfg_t *cfg, + const vpx_codec_cx_pkt_t *pkt); + +void write_webm_file_footer(struct EbmlGlobal *glob, int hash); + +#endif // WEBMENC_H_ -- 2.40.0