Commit e21d4767 authored by Vincent Penquerc'h's avatar Vincent Penquerc'h

Add VP8 support

See mapping of VP8 into ogg spec:
http://people.collabora.co.uk/~slomo/webm/ogg-vp8.pdf
parent fd85d07e
......@@ -11,7 +11,7 @@ oggz-chop \(em Extract the part of an Ogg file between given start and/or end ti
.PP
\fBoggz-chop\fR chops a section of an Ogg file.
It correctly interprets the granulepos timestamps of
Ogg CELT, CMML, Dirac, FLAC, Kate, Opus, PCM, Speex, Theora and Vorbis
Ogg CELT, CMML, Dirac, FLAC, Kate, Opus, PCM, Speex, Theora, Vorbis and VP8
bitstreams.
Run \fBoggz-known-codecs\fP\fB(1)\fP for a full list
of codecs known by the installed version of oggz.
......
......@@ -13,7 +13,7 @@ presentation time.
\fBoggz-merge\fR merges Ogg files together, interleaving
pages in order of presentation time.
It correctly interprets the granulepos timestamps of
Ogg CELT, CMML, Dirac, FLAC, Kate, Opus, PCM, Speex, Theora and Vorbis
Ogg CELT, CMML, Dirac, FLAC, Kate, Opus, PCM, Speex, Theora, Vorbis and VP8
bitstreams.
Run \fBoggz-known-codecs\fP\fB(1)\fP for a full list
of codecs known by the installed version of oggz.
......
......@@ -12,7 +12,7 @@ oggz-sort \(em Sort the pages of an Ogg file in order of presentation time.
\fBoggz-sort\fR sorts an Ogg file, interleaving
pages in order of presentation time.
It correctly interprets the granulepos timestamps of
Ogg CELT, CMML, Dirac, FLAC, Kate, Opus, PCM, Speex, Theora and Vorbis
Ogg CELT, CMML, Dirac, FLAC, Kate, Opus, PCM, Speex, Theora, Vorbis and VP8
bitstreams.
Run \fBoggz-known-codecs\fP\fB(1)\fP for a full list
of codecs known by the installed version of oggz.
......
......@@ -10,7 +10,7 @@ oggz \(em inspect and manipulate Ogg multimedia files
\fBoggz\fR is a suite of tools for manipulating
Ogg multimedia files.
It supports multiplexed files conformant with RFC3533. Oggz can parse headers for
CELT, CMML, Dirac, FLAC, Kate, Opus, PCM, Speex, Theora and Vorbis, and can read and write
CELT, CMML, Dirac, FLAC, Kate, Opus, PCM, Speex, Theora, Vorbis and VP8, and can read and write
Ogg Skeleton logical bitstreams.
.SH "Commands"
......
......@@ -118,6 +118,7 @@ typedef enum OggzStreamContent {
OGGZ_CONTENT_KATE,
OGGZ_CONTENT_DIRAC,
OGGZ_CONTENT_OPUS,
OGGZ_CONTENT_VP8,
OGGZ_CONTENT_UNKNOWN
} OggzStreamContent;
......
......@@ -67,6 +67,29 @@ oggz_metric_dirac (OGGZ * oggz, long serialno,
return units;
}
static ogg_int64_t
oggz_metric_vp8 (OGGZ * oggz, long serialno,
ogg_int64_t granulepos, void * user_data)
{
oggz_stream_t * stream;
ogg_int64_t frame;
ogg_int64_t units;
stream = oggz_get_stream (oggz, serialno);
if (stream == NULL) return -1;
frame = granulepos >> stream->granuleshift;
units = frame * stream->granulerate_d / stream->granulerate_n;
#ifdef DEBUG
printf ("oggz_..._granuleshift: serialno %010lu Got frame %lld: %lld units\n",
serialno, frame, units);
#endif
return units;
}
static ogg_int64_t
oggz_metric_default_granuleshift (OGGZ * oggz, long serialno,
ogg_int64_t granulepos, void * user_data)
......@@ -130,6 +153,10 @@ oggz_metric_update (OGGZ * oggz, long serialno)
return oggz_set_metric_internal (oggz, serialno,
oggz_metric_dirac,
NULL, 1);
} else if (oggz_stream_get_content (oggz, serialno) == OGGZ_CONTENT_VP8) {
return oggz_set_metric_internal (oggz, serialno,
oggz_metric_vp8,
NULL, 1);
} else {
return oggz_set_metric_internal (oggz, serialno,
oggz_metric_default_granuleshift,
......
......@@ -423,6 +423,39 @@ auto_opus (OGGZ * oggz, long serialno, unsigned char * data, long length, void *
return 1;
}
static int
auto_vp8 (OGGZ * oggz, long serialno, unsigned char * data, long length, void * user_data)
{
unsigned char * header = data;
ogg_int32_t gps_numerator, gps_denominator;
if (length < 26) return 0;
/* BOS header magic */
if (data[0] != 0x4f) return 0;
if (memcmp(data+1, "VP80", 4)) return 0;
if (data[5] != 1) return 0;
if (data[6] != 1) {
#ifdef DEBUG
printf("VP8 major version %u unsupported\n", data[6]);
#endif
return 0;
}
gps_numerator = int32_be_at(&header[18]);
gps_denominator = int32_be_at(&header[22]);
oggz_set_granulerate (oggz, serialno, gps_numerator,
OGGZ_AUTO_MULT * gps_denominator);
oggz_set_granuleshift (oggz, serialno, 32);
/* The Vorbis comment header is optional for VP8 */
oggz_stream_set_numheaders (oggz, serialno, 1);
return 1;
}
static int
auto_fisbone (OGGZ * oggz, long serialno, unsigned char * data, long length, void * user_data)
{
......@@ -670,6 +703,83 @@ auto_calc_opus(ogg_int64_t now, oggz_stream_t *stream, ogg_packet *op) {
}
typedef struct {
int headers_encountered;
int encountered_first_data_packet;
} auto_calc_vp8_info_t;
static ogg_int64_t
auto_calc_vp8(ogg_int64_t now, oggz_stream_t *stream, ogg_packet *op) {
int header, keyframe, visible;
auto_calc_vp8_info_t *info
= (auto_calc_vp8_info_t *)stream->calculate_data;
if (stream->calculate_data == NULL) {
stream->calculate_data = oggz_malloc(sizeof(auto_calc_vp8_info_t));
if (stream->calculate_data == NULL) return -1;
info = stream->calculate_data;
info->encountered_first_data_packet = 0;
info->headers_encountered = 1;
return 0;
}
/* headers may be repeated interleaved between data packets */
header = op->bytes == 0 || op->packet[0] == 0x4f;
keyframe = !header && op->bytes > 0 && ((op->packet[0] & 1) == 0);
visible = !header && op->bytes > 0 && (((op->packet[0]>>4) & 1) != 0);
if (header) {
info->headers_encountered += 1;
} else {
info->encountered_first_data_packet = 1;
}
if (now > -1) {
return now;
}
if (info->encountered_first_data_packet) {
if (stream->last_granulepos > 0) {
if (header) {
return stream->last_granulepos;
} else {
/* add 1 to dist, zero if keyframes
* add 1 to invcnt if invisible, set to -1 if visible
* add 1 to pts if visible
*/
ogg_int64_t pts = stream->last_granulepos >> 32;
ogg_int64_t invcnt = (stream->last_granulepos >> 30) & 3;
ogg_int64_t dist = (stream->last_granulepos >> 3) & 0x07ffffff;
ogg_int64_t granpos;
if (keyframe)
dist = 0;
else
dist++;
if (visible) {
pts++;
invcnt = 3;
} else {
if (invcnt == 3)
invcnt = 0;
else
invcnt++;
}
granpos = (pts<<32) | (invcnt<<30) | (dist<<3) | 0;
return granpos;
}
}
return -1;
}
return 0;
}
/*
* Header packets are marked by a set MSB in the first byte. Inter packets
* are marked by a set 2MSB in the first byte. Intra packets (keyframes)
......@@ -1220,6 +1330,7 @@ const oggz_auto_contenttype_t oggz_auto_codec_ident[] = {
{"\200kate\0\0\0", 8, "Kate", auto_kate, NULL, NULL},
{"BBCD\0", 5, "Dirac", auto_dirac, NULL, NULL},
{"OpusHead", 8, "Opus", auto_opus, auto_calc_opus, NULL},
{"\x4fVP80", 5, "VP8", auto_vp8, auto_calc_vp8, NULL},
{"", 0, "Unknown", NULL, NULL, NULL}
};
......@@ -1349,6 +1460,11 @@ oggz_auto_read_comments (OGGZ * oggz, oggz_stream_t * stream, long serialno,
offset = 8;
}
break;
case OGGZ_CONTENT_VP8:
if (op->bytes > 7 && memcmp (op->packet, "\x4fVP80\002 ", 7) == 0) {
offset = 7;
}
break;
default:
break;
}
......
......@@ -832,6 +832,8 @@ oggz_comment_generate(OGGZ * oggz, long serialno,
{0x81, 0x6b, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, 0x00};
const unsigned char preamble_opus[8] =
{'O', 'p', 'u', 's', 'T', 'a', 'g', 's'};
const unsigned char preamble_vp8[7] =
{0x4f, 'V', 'P', '8', '0', 002, ' '};
switch(packet_type) {
......@@ -855,6 +857,10 @@ oggz_comment_generate(OGGZ * oggz, long serialno,
preamble_length = sizeof preamble_opus;
preamble = preamble_opus;
break;
case OGGZ_CONTENT_VP8:
preamble_length = sizeof preamble_vp8;
preamble = preamble_vp8;
break;
case OGGZ_CONTENT_PCM:
case OGGZ_CONTENT_SPEEX:
preamble_length = 0;
......@@ -915,7 +921,7 @@ oggz_comment_generate(OGGZ * oggz, long serialno,
return c_packet;
}
/* In Flac, OggPCM, Speex, Theora, Vorbis, Kate, and Opus the comment packet will
/* In Flac, OggPCM, Speex, Theora, Vorbis, Kate, Opus and VP8, the comment packet will
be second in the stream, i.e. packetno=1, and it will have granulepos=0 */
ogg_packet *
oggz_comments_generate(OGGZ * oggz, long serialno,
......
......@@ -18,6 +18,7 @@ const char * const mime_type_names[] = {
"application/x-kate",
"video/dirac",
"audio/x-opus",
"video/vp8",
NULL /* UNKNOWN */
};
......
......@@ -744,6 +744,59 @@ read_dirac (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
return OGGZ_CONTINUE;
}
static int
read_vp8 (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
{
OCState * state = (OCState *)user_data;
OCTrackState * ts;
OCPageAccum * pa;
double page_time;
ogg_int64_t granulepos, pts, dist, keyframe;
int accum_size;
page_time = oggz_tell_units (oggz) / 1000.0;
ts = oggz_table_lookup (state->tracks, serialno);
accum_size = oggz_table_size (ts->page_accum);
if (page_time >= state->start) {
/* Glue in fisbones, write out accumulated pages */
chop_glue (state, oggz);
/* Switch to the plain page reader */
oggz_set_read_page (oggz, serialno, read_plain, state);
return read_plain (oggz, og, serialno, user_data);
} /* else { ... */
granulepos = ogg_page_granulepos (OGG_PAGE_CONST(og));
if (granulepos != -1) {
pts = granulepos >> 32;
dist = (granulepos >> 3) & 0x7ffffff;
keyframe = pts - dist;
if (keyframe != ts->prev_keyframe) {
if (ogg_page_continued(OGG_PAGE_CONST(og))) {
/* If this new-keyframe page is continued, advance the page accumulator,
* ie. recover earlier pages from this new GOP */
accum_size = track_state_advance_page_accum (ts);
} else {
/* Otherwise, just clear the page accumulator */
track_state_remove_page_accum (ts);
accum_size = 0;
}
/* Record this as prev_keyframe */
ts->prev_keyframe = keyframe;
}
}
/* Add a copy of this to the page accumulator */
pa = page_accum_new (og, page_time);
oggz_table_insert (ts->page_accum, accum_size, pa);
return OGGZ_CONTINUE;
}
/*
* OggzReadPageCallback read_headers
*
......@@ -781,6 +834,8 @@ read_headers (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
oggz_set_read_page (oggz, serialno, read_plain, state);
} else if (content_type == OGGZ_CONTENT_DIRAC) {
oggz_set_read_page (oggz, serialno, read_dirac, state);
} else if (content_type == OGGZ_CONTENT_VP8) {
oggz_set_read_page (oggz, serialno, read_vp8, state);
} else {
oggz_set_read_page (oggz, serialno, read_gs, state);
}
......
......@@ -268,9 +268,9 @@ static int
read_packet (OGGZ * oggz, oggz_packet * zp, long serialno, void * user_data)
{
OCData * ocdata = (OCData *)user_data;
ogg_packet * op = &zp->op;
ogg_packet * op = &zp->op, * copy_op = NULL;
const char * vendor;
int flush;
int flush, copy_op_flush = 0;
int ret;
#ifdef USE_FLUSH_NEXT
......@@ -290,6 +290,19 @@ read_packet (OGGZ * oggz, oggz_packet * zp, long serialno, void * user_data)
/* Edit the packet data if required */
if (filter_stream_p (ocdata, serialno) && op->packetno == 1) {
/* For VP8, the comment packet is optional, so we may be in the case
where we need to write comments, but there is no comments packet
in the stream. In that case, packetno 1 will not be a comments
packet to replace, so we must insert the comments, and then copy
packetno 1 (which will now be packetno 2) to the output */
if (oggz_stream_get_content (oggz, serialno) == OGGZ_CONTENT_VP8 &&
oggz_stream_get_numheaders (oggz, serialno) == 1) {
copy_op = op;
copy_op->packetno = -1;
copy_op_flush = flush;
flush = 1;
}
vendor = oggz_comment_get_vendor (ocdata->reader, serialno);
/* Copy across the comments, unless "delete comments before editing" */
......@@ -307,10 +320,17 @@ read_packet (OGGZ * oggz, oggz_packet * zp, long serialno, void * user_data)
}
/* Feed the packet into the writer */
op->packetno = -1; /* rewrite packetno in case we need to insert a packet */
if ((ret = oggz_write_feed (ocdata->writer, op, serialno, flush, NULL)) != 0)
fprintf (stderr, "oggz_write_feed: %d\n", ret);
return more_headers (ocdata, op, serialno);
ret = more_headers (ocdata, op, serialno);
if (ret == OGGZ_CONTINUE && copy_op) {
if ((ret = oggz_write_feed (ocdata->writer, copy_op, serialno, copy_op_flush, NULL)) != 0)
fprintf (stderr, "oggz_write_feed: %d\n", ret);
}
return ret;
}
static int
......@@ -371,7 +391,7 @@ read_comments(OGGZ *oggz, oggz_packet * zp, long serialno, void *user_data)
OCData * ocdata = (OCData *)user_data;
ogg_packet * op = &zp->op;
const OggzComment * comment;
const char * codec_name;
const char * codec_name, * vendor;
if (filter_stream_p (ocdata, serialno) && op->packetno == 1) {
codec_name = oggz_stream_get_content_type(oggz, serialno);
......@@ -381,7 +401,8 @@ read_comments(OGGZ *oggz, oggz_packet * zp, long serialno, void *user_data)
printf ("???: serialno %010lu\n", serialno);
}
printf("\tVendor: %s\n", oggz_comment_get_vendor(oggz, serialno));
vendor = oggz_comment_get_vendor(oggz, serialno);
if (vendor) printf("\tVendor: %s\n", vendor);
for (comment = oggz_comment_first(oggz, serialno); comment;
comment = oggz_comment_next(oggz, serialno, comment))
......
......@@ -148,15 +148,21 @@ gp_to_granule (OGGZ * oggz, long serialno, ogg_int64_t granulepos)
{
int granuleshift;
ogg_int64_t iframe, pframe, granule;
OggzStreamContent content;
granuleshift = oggz_get_granuleshift (oggz, serialno);
content = oggz_stream_get_content (oggz, serialno);
iframe = granulepos >> granuleshift;
pframe = granulepos - (iframe << granuleshift);
granule = iframe + pframe;
if (content == OGGZ_CONTENT_VP8) {
granule = granulepos >> granuleshift;
} else {
iframe = granulepos >> granuleshift;
pframe = granulepos - (iframe << granuleshift);
granule = iframe + pframe;
if (oggz_stream_get_content (oggz, serialno) == OGGZ_CONTENT_DIRAC)
granule >>= 9;
if (content == OGGZ_CONTENT_DIRAC)
granule >>= 9;
}
return granule;
}
......
......@@ -168,15 +168,21 @@ gp_to_granule (OGGZ * oggz, long serialno, ogg_int64_t granulepos)
{
int granuleshift;
ogg_int64_t iframe, pframe, granule;
OggzStreamContent content;
granuleshift = oggz_get_granuleshift (oggz, serialno);
content = oggz_stream_get_content (oggz, serialno);
iframe = granulepos >> granuleshift;
pframe = granulepos - (iframe << granuleshift);
granule = iframe+pframe;
if (content == OGGZ_CONTENT_VP8) {
granule = granulepos >> granuleshift;
} else {
iframe = granulepos >> granuleshift;
pframe = granulepos - (iframe << granuleshift);
granule = iframe+pframe;
if (oggz_stream_get_content (oggz, serialno) == OGGZ_CONTENT_DIRAC)
granule >>= 9;
if (content == OGGZ_CONTENT_DIRAC)
granule >>= 9;
}
return granule;
}
......
......@@ -47,7 +47,7 @@ usage (char * progname)
printf ("oggz is a commandline tool for manipulating Ogg files. It supports\n"
"multiplexed files conformant with RFC3533. Oggz can parse headers for\n"
"CELT, CMML, FLAC, Kate, Opus, PCM, Speex, Theora and Vorbis, and can read and write\n"
"CELT, CMML, FLAC, Kate, Opus, PCM, Speex, Theora, Vorbis and VP8, and can read and write\n"
"Ogg Skeleton logical bitstreams.\n");
printf ("\nCommands:\n");
......
......@@ -42,6 +42,8 @@
#include "dirac.h"
#include "oggz_tools_dirac.h"
#include "oggz_tools_vp8.h"
#if defined (WIN32) || defined (__EMX__)
#include <fcntl.h>
#include <io.h>
......@@ -293,6 +295,27 @@ ot_opus_info (unsigned char * data, long len)
return buf;
}
static char *
ot_vp8_info (unsigned char * data, long len)
{
char * buf;
const size_t nbytes = 192;
if (len < 26) return NULL;
buf = malloc (nbytes);
snprintf (buf, nbytes,
"\tVP8-Mapping-Version: %u.%u\n"
"\tVideo-Framerate: %.3f fps\n"
"\tVideo-Width: %d\n\tVideo-Height: %d\n",
data[6], data[7],
(double)INT32_BE_AT(&data[18])/ (double)INT32_BE_AT(&data[22]),
INT16_BE_AT(&data[8]), INT16_BE_AT(&data[10]));
return buf;
}
static char *
ot_dirac_info (unsigned char * data, long len)
{
......@@ -371,6 +394,7 @@ static const OTCodecInfoFunc codec_ident[] = {
ot_kate_info, /* KATE */
ot_dirac_info, /* BBCD */
ot_opus_info, /* OPUS */
ot_vp8_info, /* VP8 */
NULL /* UNKNOWN */
};
......@@ -476,6 +500,17 @@ ot_dirac_gpos_parse (ogg_int64_t iframe, ogg_int64_t pframe,
dg->dt = (ogg_int64_t)dg->pt - dg->delay;
}
void
ot_vp8_gpos_parse (ogg_int64_t iframe, ogg_int64_t pframe,
struct ot_vp8_gpos * vg)
{
vg->pts = iframe;
vg->invcnt = (pframe >> 30) & 3;
if (vg->invcnt == 3) /* -1 on 2 bits */
vg->invcnt = -1;
vg->dist = (pframe >> 3) & 0x7ffffff;
}
int
ot_fprint_granulepos (FILE * stream, OGGZ * oggz, long serialno,
ogg_int64_t granulepos)
......@@ -486,20 +521,28 @@ ot_fprint_granulepos (FILE * stream, OGGZ * oggz, long serialno,
ret = fprintf (stream, "%" PRId64, granulepos);
} else {
ogg_int64_t iframe, pframe;
OggzStreamContent content;
iframe = granulepos >> granuleshift;
pframe = granulepos - (iframe << granuleshift);
if (oggz_stream_get_content (oggz, serialno) != OGGZ_CONTENT_DIRAC) {
ret = fprintf (stream, "%" PRId64 "|%" PRId64, iframe, pframe);
} else {
content = oggz_stream_get_content (oggz, serialno);
if (content == OGGZ_CONTENT_DIRAC) {
struct ot_dirac_gpos dg;
ot_dirac_gpos_parse (iframe, pframe, &dg);
ret = fprintf (stream,
"(pt:%u,dt:%" PRId64 ",dist:%hu,delay:%hu)",
dg.pt, dg.dt, dg.dist, dg.delay);
} else if (content == OGGZ_CONTENT_VP8) {
struct ot_vp8_gpos vg;
ot_vp8_gpos_parse (iframe, pframe, &vg);
ret = fprintf (stream,
"(pts:%u,invcnt:%hd,dist:%hu)",
vg.pts, vg.invcnt, vg.dist);
} else {
ret = fprintf (stream, "%" PRId64 "|%" PRId64, iframe, pframe);
}
}
}
return ret;
}
......
/*
Copyright (C) 2008 Annodex Association
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Annodex Association or the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE ASSOCIATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __OGGZ_TOOLS_VP8_H__
#define __OGGZ_TOOLS_VP8_H__
#include "config.h"
/*
* VP8 specific granulepos interpretation
*/
struct ot_vp8_gpos {
ogg_uint32_t pts;
ogg_int16_t invcnt;
ogg_uint16_t dist;
};
/* Parse a granulepos value using VP8 interpretation */
void ot_vp8_gpos_parse (ogg_int64_t iframe, ogg_int64_t pframe,
struct ot_vp8_gpos * vg);
#endif /* __OGGZ_TOOLS_VP8_H__ */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment