Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
L
libjitsi
Manage
Activity
Members
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Releases
Model registry
Analyze
Contributor analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
ZRTP
libjitsi
Commits
4e7a5785
Commit
4e7a5785
authored
10 years ago
by
Boris Grozev
Browse files
Options
Downloads
Patches
Plain Diff
Adds a WebmWriter.
parent
f0987329
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/native/vpx/org_jitsi_impl_neomedia_recording_WebmWriter.cc
+478
-0
478 additions, 0 deletions
...ative/vpx/org_jitsi_impl_neomedia_recording_WebmWriter.cc
src/org/jitsi/impl/neomedia/recording/WebmWriter.java
+78
-0
78 additions, 0 deletions
src/org/jitsi/impl/neomedia/recording/WebmWriter.java
with
556 additions
and
0 deletions
src/native/vpx/org_jitsi_impl_neomedia_recording_WebmWriter.cc
0 → 100644
+
478
−
0
View file @
4e7a5785
#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
,
"
\n
Failed 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
);
}
This diff is collapsed.
Click to expand it.
src/org/jitsi/impl/neomedia/recording/WebmWriter.java
0 → 100644
+
78
−
0
View file @
4e7a5785
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
;
}
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment