Skip to content
Snippets Groups Projects
Commit 4e7a5785 authored by Boris Grozev's avatar Boris Grozev
Browse files

Adds a WebmWriter.

parent f0987329
No related branches found
No related tags found
No related merge requests found
#include <assert.h>
#include <jni.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <new>
#include "vpx/vpx_codec.h"
#include "vpx/vpx_encoder.h"
extern "C" {
#include "libmkv/EbmlWriter.h"
#include "libmkv/EbmlIDs.h"
}
#define LITERALU64(n) n##LLU
#ifdef NDEBUG
# define printf(fmt, ...)
#else
# ifdef ANDROID_NDK
# include <android/log.h>
# define printf(fmt, ...) \
__android_log_print(ANDROID_LOG_DEBUG, "LIBVPX_WEBM", fmt, ##__VA_ARGS__)
# else
# define printf(fmt, ...) \
printf(fmt "\n", ##__VA_ARGS__)
# endif
#endif
#define FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE Java_org_jitsi_impl_neomedia_recording_WebmWriter_ ## NAME \
(JNIEnv * env, jobject thiz, ##__VA_ARGS__);\
} \
JNIEXPORT RETURN_TYPE Java_org_jitsi_impl_neomedia_recording_WebmWriter_ ## NAME \
(JNIEnv * env, jobject thiz, ##__VA_ARGS__)\
#define STRING_RETURN(JNI_NAME, LIBVPX_NAME) \
FUNC(jstring, JNI_NAME) { \
printf(#JNI_NAME); \
return env->NewStringUTF(LIBVPX_NAME()); \
}
/* 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;
typedef off_t EbmlLoc;
struct cue_entry {
unsigned int time;
uint64_t loc;
};
extern "C" {
struct EbmlGlobal {
FILE *stream;
int64_t last_pts_ms;
/* 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;
};
}
FUNC(jlong, allocCfg) {
EbmlGlobal *glob = new (std::nothrow) EbmlGlobal;
if (glob) {
memset(glob, 0, sizeof(*glob));
glob->last_pts_ms = -1;
}
return (intptr_t)glob;
}
FUNC(void, freeCfg, jlong jglob) {
const EbmlGlobal *glob = reinterpret_cast<EbmlGlobal*>(jglob);
if (glob != NULL) {
if (glob->stream)
fclose(glob->stream);
free(glob->cue_list);
delete glob;
glob = 0;
}
}
FUNC(jboolean, openFile, jlong jglob, jstring fileName) {
EbmlGlobal *glob = reinterpret_cast<EbmlGlobal*>(jglob);
const char *mfile = env->GetStringUTFChars(fileName, 0);
glob->stream = fopen(mfile, "wb");
env->ReleaseStringUTFChars(fileName, mfile);
if (!glob->stream)
return JNI_TRUE;
return JNI_FALSE;
}
extern "C" {
void Ebml_Write(EbmlGlobal *glob, const void *buffer_in, unsigned long len) {
if (fwrite(buffer_in, 1, len, glob->stream))
;
}
}
#define WRITE_BUFFER(s) \
for (i = len - 1; i >= 0; i--) { \
x = *(const s *)buffer_in >> (i * CHAR_BIT); \
Ebml_Write(glob, &x, 1ULL); \
}
extern "C" {
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,
uint64_t class_id, uint64_t ui) {
unsigned char sizeSerialized = 4 | 0x80;
Ebml_WriteID(glob, class_id);
Ebml_Serialize(glob, &sizeSerialized, sizeof(sizeSerialized), 1ULL);
Ebml_Serialize(glob, &ui, sizeof(ui), 4ULL);
}
static void Ebml_StartSubElement(EbmlGlobal *glob, EbmlLoc *ebmlLoc,
uint64_t 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 = ftello(glob->stream);
Ebml_Serialize(glob, &unknownLen, sizeof(unknownLen), 8ULL);
}
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(0x0100000000000000);
/* 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), 8ULL);
/* Reset the stream pointer */
fseeko(glob->stream, pos, SEEK_SET);
}
static void write_webm_seek_element(EbmlGlobal *ebml, uint64_t 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 = 45; //approx. the duration of a single frame (in ms).
char version_string[64];
/* Assemble version string */
strcpy(version_string, "vpxenc ");
strncat(version_string,
vpx_codec_version_str(),
sizeof(version_string) - 1 - strlen(version_string));
ebml->segment_info_pos = ftello(ebml->stream);
Ebml_StartSubElement(ebml, &startInfo, Info);
Ebml_SerializeUnsigned(ebml, TimecodeScale, 1000000);
Ebml_SerializeFloat(ebml, Segment_Duration,
ebml->last_pts_ms + frame_time);
Ebml_SerializeString(ebml, MuxingApp, version_string);
Ebml_SerializeString(ebml, WritingApp, version_string);
Ebml_EndSubElement(ebml, &startInfo);
}
}
FUNC(void, writeWebmFileHeader, jlong jglob,
jint width, jint height) {
EbmlGlobal *glob = reinterpret_cast<EbmlGlobal*>(jglob);
stereo_format_t stereo_fmt = STEREO_FORMAT_MONO;
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 = ftello(glob->stream);
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); // video is always 1
Ebml_SerializeString(glob, CodecID, "V_VP8");
{
unsigned int pixelWidth = width;
unsigned int pixelHeight = height;
EbmlLoc videoStart;
Ebml_StartSubElement(glob, &videoStart, Video);
Ebml_SerializeUnsigned(glob, PixelWidth, pixelWidth);
Ebml_SerializeUnsigned(glob, PixelHeight, pixelHeight);
Ebml_SerializeUnsigned(glob, StereoMode, stereo_fmt);
//Ebml_SerializeFloat(glob, FrameRate, frameRate);
Ebml_EndSubElement(glob, &videoStart); // Video
}
Ebml_EndSubElement(glob, &start); // Track Entry
}
Ebml_EndSubElement(glob, &trackStart);
}
// segment element is open
}
}
FUNC(void, writeWebmBlock, jlong jglob, jobject jfd) {
EbmlGlobal *glob = reinterpret_cast<EbmlGlobal*>(jglob);
jclass frameDescriptor = env->FindClass("org/jitsi/impl/neomedia/recording/WebmWriter$FrameDescriptor");
assert(frameDescriptor != NULL);
jfieldID bufferId = env->GetFieldID(frameDescriptor, "buffer", "[B");
assert(bufferId != NULL);
jfieldID offsetId = env->GetFieldID(frameDescriptor, "offset", "I");
assert(offsetId != NULL);
jfieldID lengthId = env->GetFieldID(frameDescriptor, "length", "J");
assert(lengthId != NULL);
jfieldID flagsId = env->GetFieldID(frameDescriptor, "flags", "I");
assert(flagsId != NULL);
jfieldID ptsId = env->GetFieldID(frameDescriptor, "pts", "J");
assert(ptsId != NULL);
jobject jba = env->GetObjectField(jfd, bufferId);
assert(jba != NULL);
jint offset = env->GetIntField(jfd, offsetId);
jint frameFlags = env->GetIntField(jfd, flagsId);
uint64_t 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 = env->GetLongField(jfd, ptsId);
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;
is_keyframe = (frameFlags & 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 = 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 = reinterpret_cast<cue_entry*>(realloc(glob->cue_list,
(glob->cues+1) * sizeof(struct cue_entry)));
if (new_cue_list) {
glob->cue_list = new_cue_list;
} else {
// TODO(frkoenig) : Handle this error better/correctly
fprintf(stderr, "\nFailed to realloc cue list.\n");
exit(EXIT_FAILURE);
}
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);
jlong frameSz = env->GetLongField(jfd, lengthId);
block_length = frameSz + 4;
block_length |= 0x10000000;
Ebml_Serialize(glob, &block_length, sizeof(block_length), 4ULL);
track_number = 1;
track_number |= 0x80;
Ebml_Write(glob, &track_number, 1ULL);
Ebml_Serialize(glob, &block_timecode, sizeof(block_timecode), 2ULL);
flags = 0;
if (is_keyframe)
flags |= 0x80;
if (frameFlags & VPX_FRAME_IS_INVISIBLE)
flags |= 0x08;
Ebml_Write(glob, &flags, 1ULL);
jbyte *frameBuf = env->GetByteArrayElements((jbyteArray)jba, 0);
Ebml_Write(glob, frameBuf + offset, static_cast<uint64_t>(frameSz));
env->ReleaseByteArrayElements((jbyteArray)jba, frameBuf, 0);
}
FUNC(void, writeWebmFileFooter, jlong jglob, jlong hash) {
EbmlGlobal *glob = reinterpret_cast<EbmlGlobal*>(jglob);
//1
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);
//2
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);
}
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, hash);
fseeko(glob->stream, 0, SEEK_END);
}
package org.jitsi.impl.neomedia.recording;
import java.io.*;
public class WebmWriter
{
static
{
System.loadLibrary("jnvpx");
}
/**
* Constant corresponding to <tt>VPX_FRAME_IS_KEY</tt> from libvpx's
* <tt>vpx/vpx_encoder.h</tt>
*/
public static int FLAG_FRAME_IS_KEY = 0x01;
/**
* Constant corresponding to <tt>VPX_FRAME_IS_INVISIBLE</tt> from libvpx's
* <tt>vpx/vpx_encoder.h</tt>
*/
public static int FLAG_FRAME_IS_INVISIBLE = 0x04;
private long glob;
private native long allocCfg();
/**
* Free-s <tt>glob</tt> and closes the file opened for writing.
* @param glob
*/
private native void freeCfg(long glob);
private native boolean openFile(long glob, String fileName);
private native void writeWebmFileHeader(long glob, int width, int height);
public void writeWebmFileHeader(int width, int height)
{
writeWebmFileHeader(glob, width, height);
}
private native void writeWebmBlock(long glob, FrameDescriptor fd);
private native void writeWebmFileFooter(long glob, long hash);
public WebmWriter(String filename)
throws IOException
{
glob = allocCfg();
if (glob == 0)
{
throw new IOException("allocCfg() failed");
}
if (openFile(glob, filename))
{
throw new IOException("Can not open " + filename + " for writing");
}
}
public void close()
{
writeWebmFileFooter(glob, 0);
freeCfg(glob); //also closes the file
}
public void writeFrame(FrameDescriptor fd)
{
writeWebmBlock(glob, fd);
}
public static class FrameDescriptor
{
public byte[] buffer;
public int offset;
public long length;
public long pts;
public int flags;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment