blob: 3fb345054d70802a7c93a1a3669663b41c3e4343 [file] [log] [blame]
/*
* 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.
*/
// This is a simple program that wraps vp8 raw data to webm format.
// The code was copied from libvpx vpxenc.c
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#include "libmkv/EbmlWriter.h"
#include "libmkv/EbmlIDs.h"
#include "third_party/libvpx/webm.h"
#include "yavta.h"
#define LITERALU64(n) n##LLU
#define MAX_OUTPUT 1024
#define MAX_CUE_SIZE 64
#define STEREO_FORMAT_MONO 0
typedef off_t EbmlLoc;
struct cue_entry
{
unsigned int time;
uint64_t loc;
};
struct EbmlGlobal
{
int64_t last_pts_ms;
int framerate_den;
int framerate_num;
/* These pointers are to the start of an element */
off_t position_reference;
off_t segment_info_pos;
off_t track_pos;
off_t cue_pos;
off_t cluster_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;
unsigned char *buf;
unsigned int buf_max_length;
unsigned int length;
unsigned int offset;
};
struct stream_state
{
int g_timebase_den;
int g_timebase_num;
int width;
int height;
int frame_num; // number of frames written
EbmlGlobal ebml;
};
// Global variables.
struct stream_state *streams[WEBM_MAX_NUM_STREAMS];
void Ebml_Write(EbmlGlobal *glob, const void *buffer_in, unsigned long len)
{
unsigned char *src;
if (glob->offset + len > glob->buf_max_length) {
printf("Buffer too small!");
exit(1);
}
src = glob->buf;
src += glob->offset;
memcpy(src, buffer_in, len);
glob->offset += len;
if (glob->offset > glob->length) glob->length = glob->offset;
}
#define WRITE_BUFFER(s) \
for(i = len-1; i>=0; i--)\
{ \
x = *(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(0x01FFFFFFFFFFFFFF);
Ebml_WriteID(glob, class_id);
*ebmlLoc = glob->offset;
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 = glob->offset;
/* Calculate the size of this element */
size = pos - *ebmlLoc - 8;
size |= LITERALU64(0x0100000000000000);
/* Seek back to the beginning of the element and write the new size */
glob->offset = *ebmlLoc;
Ebml_Serialize(glob, &size, sizeof(size), 8);
/* Reset the stream pointer */
glob->offset = pos;
}
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)
{
EbmlLoc start;
EbmlLoc startInfo;
uint64_t frame_time;
char version_string[64];
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
/* Assemble version string */
strcpy(version_string, "vp8_to_web8");
frame_time = (uint64_t)1000 * ebml->framerate_den / ebml->framerate_num;
ebml->segment_info_pos = ebml->offset;
Ebml_StartSubElement(ebml, &startInfo, Info);
Ebml_SerializeUnsigned(ebml, TimecodeScale, 1000000);
Ebml_SerializeFloat(ebml, Segment_Duration,
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,
int width,
int height,
int framerate_den,
int framerate_num)
{
{
EbmlLoc start;
Ebml_StartSubElement(glob, &start, EBML);
Ebml_SerializeUnsigned(glob, EBMLVersion, 1);
Ebml_SerializeUnsigned(glob, EBMLReadVersion, 1); //EBML Read Version
Ebml_SerializeUnsigned(glob, EBMLMaxIDLength, 4); //EBML Max ID Length
Ebml_SerializeUnsigned(glob, EBMLMaxSizeLength, 8); //EBML Max Size Length
Ebml_SerializeString(glob, DocType, "webm"); //Doc Type
Ebml_SerializeUnsigned(glob, DocTypeVersion, 2); //Doc Type Version
Ebml_SerializeUnsigned(glob, DocTypeReadVersion, 2); //Doc Type Read Version
Ebml_EndSubElement(glob, &start);
}
{
Ebml_StartSubElement(glob, &glob->startSegment, Segment); //segment
glob->position_reference = glob->offset;
glob->framerate_den = framerate_den;
glob->framerate_num = framerate_num;
write_webm_seek_info(glob);
{
EbmlLoc trackStart;
glob->track_pos = glob->offset;
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);
Ebml_SerializeUnsigned32(glob, TrackUID, trackID);
Ebml_SerializeUnsigned(glob, TrackType, 1); //video is always 1
Ebml_SerializeString(glob, CodecID, "V_VP8");
{
unsigned int pixelWidth = width;
unsigned int pixelHeight = height;
//float frameRate = (float)framerate_num/(float)framerate_den;
EbmlLoc videoStart;
Ebml_StartSubElement(glob, &videoStart, Video);
Ebml_SerializeUnsigned(glob, PixelWidth, pixelWidth);
Ebml_SerializeUnsigned(glob, PixelHeight, pixelHeight);
Ebml_SerializeUnsigned(glob, StereoMode, STEREO_FORMAT_MONO);
// Since we are streaming, skip frame rate
//Ebml_SerializeFloat(glob, FrameRate, frameRate);
Ebml_EndSubElement(glob, &videoStart); //Video
}
Ebml_EndSubElement(glob, &start); //Track Entry
}
Ebml_EndSubElement(glob, &trackStart);
}
// segment element is open
}
}
static void
write_webm_block(EbmlGlobal *glob,
int g_timebase_num, int g_timebase_den,
int is_keyframe,
size_t sz, // length of compressed data
int64_t pts // time stamp to show frame (in timebase)
)
{
unsigned long block_length;
unsigned char track_number;
unsigned short block_timecode = 0;
unsigned char flags;
int64_t pts_ms;
int start_cluster = 0;
/* Calculate the PTS of this frame in milliseconds */
pts_ms = pts * 1000
* (uint64_t)g_timebase_num / (uint64_t)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 = pts_ms - glob->cluster_timecode;
// In order to send frames one by one, make one cluster for one frame
start_cluster = 1;
if(start_cluster || is_keyframe)
{
// Do not seek back.
//if(glob->cluster_open)
// Ebml_EndSubElement(glob, &glob->startCluster);
/* Open the new cluster */
block_timecode = 0;
glob->cluster_open = 1;
glob->cluster_timecode = pts_ms;
glob->cluster_pos = glob->offset;
Ebml_StartSubElement(glob, &glob->startCluster, Cluster); //cluster
Ebml_SerializeUnsigned(glob, Timecode, glob->cluster_timecode);
#if 0 // Don't need this for streaming
/* 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 {
log_err("Failed to realloc cue list.");
exit(EXIT_FAILURE);
}
cue = &glob->cue_list[glob->cues];
cue->time = glob->cluster_timecode;
cue->loc = glob->cluster_pos;
glob->cues++;
}
#endif
}
/* Write the Simple Block */
Ebml_WriteID(glob, SimpleBlock);
block_length = 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;
Ebml_Write(glob, &flags, 1);
// end Cluster for each frame
if(glob->cluster_open) {
// In order to let Ebml_EndSubElement calculate correct size (including frame data),
// shift offset and length. But do not really copy frame data.
glob->offset += sz;
glob->length += sz;
Ebml_EndSubElement(glob, &glob->startCluster);
glob->cluster_open = 0;
// Shift back.
glob->offset -= sz;
glob->length -= sz;
}
}
static void write_webm_file_footer(EbmlGlobal *glob)
{
// Do not seek back.
//if(glob->cluster_open)
// Ebml_EndSubElement(glob, &glob->startCluster);
{
EbmlLoc start;
unsigned int i;
glob->cue_pos = glob->offset;
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_SerializeUnsigned(glob, CueBlockNumber, cue->blockNumber);
Ebml_EndSubElement(glob, &start);
}
Ebml_EndSubElement(glob, &start);
}
Ebml_EndSubElement(glob, &start);
}
// Do not seek back.
//Ebml_EndSubElement(glob, &glob->startSegment);
}
// return: output buffer
// output_size: the size of output buffer
unsigned char *webm_get_file_header(int stream_index, int width, int height, int *output_size) {
struct stream_state *stream;
if (stream_index < 0 || stream_index >= WEBM_MAX_NUM_STREAMS) {
log_err("Invalid stream index: %d", stream_index);
return NULL;
}
if (output_size == NULL) {
log_err("Output size cannot be null!");
return NULL;
}
streams[stream_index] = calloc(1, sizeof(struct stream_state));
stream = streams[stream_index];
if(!stream) {
log_err("Failed to allocate new stream.");
return NULL;
}
/* Setup default input stream settings */
/* Change the default timebase to a high enough value so that the
* encoder will always create strictly increasing timestamps.
*/
stream->g_timebase_den = 1000;
stream->g_timebase_num = 1;
stream->frame_num = 0;
stream->ebml.last_pts_ms = -1;
stream->ebml.buf = (unsigned char *) malloc(MAX_OUTPUT);
stream->ebml.buf_max_length = MAX_OUTPUT;
stream->ebml.length = 0;
stream->ebml.offset = 0;
memset(stream->ebml.buf, 0, stream->ebml.buf_max_length);
write_webm_file_header(&stream->ebml, width, height, 1, 30);
*output_size = stream->ebml.offset;
return stream->ebml.buf;
}
unsigned char *webm_get_block_header(int stream_index, int is_iframe,
int buffer_size, int *output_size) {
struct stream_state *stream;
if (stream_index < 0 || stream_index >= WEBM_MAX_NUM_STREAMS) {
log_err("Invalid stream index: %d", stream_index);
return NULL;
}
if (output_size == NULL) {
log_err("Output size cannot be null!");
return NULL;
}
stream = streams[stream_index];
stream->ebml.buf = (unsigned char *) malloc(MAX_OUTPUT);
stream->ebml.buf_max_length = MAX_OUTPUT;
stream->ebml.length = 0;
stream->ebml.offset = 0;
write_webm_block(&stream->ebml, stream->g_timebase_num,
stream->g_timebase_den, is_iframe, buffer_size,
stream->frame_num);
stream->frame_num++;
*output_size = stream->ebml.offset;
return stream->ebml.buf;
}
unsigned char *webm_get_file_footer(int stream_index, int *output_size) {
struct stream_state *stream;
unsigned char *output_buffer;
int malloc_size;
if (stream_index < 0 || stream_index >= WEBM_MAX_NUM_STREAMS) {
log_err("Invalid stream index: %d", stream_index);
return NULL;
}
if (output_size == NULL) {
log_err("Output size cannot be null!");
return NULL;
}
stream = streams[stream_index];
malloc_size = MAX_OUTPUT + stream->ebml.cues * MAX_CUE_SIZE;
stream->ebml.buf = (unsigned char *) malloc(malloc_size);
stream->ebml.buf_max_length = malloc_size;
stream->ebml.length = 0;
stream->ebml.offset = 0;
write_webm_file_footer(&stream->ebml);
free(stream->ebml.cue_list);
stream->ebml.cue_list = NULL;
*output_size = stream->ebml.offset;
output_buffer = stream->ebml.buf;
return output_buffer;
}