diff --git a/src/add-ons/media/plugins/ogg/OggSpeexSeekable.cpp b/src/add-ons/media/plugins/ogg/OggSpeexSeekable.cpp new file mode 100644 index 0000000000..c17c61bb1a --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggSpeexSeekable.cpp @@ -0,0 +1,196 @@ +#include "OggSpeexFormats.h" +#include "OggSpeexSeekable.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * speex header from libspeex/speex_header.h + * also documented at http://www.speex.org/manual/node7.html#SECTION00073000000000000000 + */ + +typedef struct SpeexHeader { + char speex_string[8]; /**< Identifies a Speex bit-stream, always set to "Speex " */ + char speex_version[20]; /**< Speex version */ + int speex_version_id; /**< Version for Speex (for checking compatibility) */ + int header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ + int rate; /**< Sampling rate used */ + int mode; /**< Mode used (0 for narrowband, 1 for wideband) */ + int mode_bitstream_version; /**< Version ID of the bit-stream */ + int nb_channels; /**< Number of channels encoded */ + int bitrate; /**< Bit-rate used */ + int frame_size; /**< Size of frames */ + int vbr; /**< 1 for a VBR encoding, 0 otherwise */ + int frames_per_packet; /**< Number of frames stored per Ogg packet */ + int extra_headers; /**< Number of additional headers after the comments */ + int reserved1; /**< Reserved for future use, must be zero */ + int reserved2; /**< Reserved for future use, must be zero */ +} SpeexHeader; + + +/* + * OggSpeexSeekable implementations + */ + +/* static */ bool +OggSpeexSeekable::IsValidHeader(const ogg_packet & packet) +{ + return findIdentifier(packet,"Speex ",0); +} + +OggSpeexSeekable::OggSpeexSeekable(long serialno) + : OggSeekable(serialno) +{ + TRACE("OggSpeexSeekable::OggSpeexSeekable\n"); +} + +OggSpeexSeekable::~OggSpeexSeekable() +{ + +} + +status_t +OggSpeexSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggSpeexSeekable::GetStreamInfo\n"); + status_t result = B_OK; + ogg_packet packet; + + // get header packet + if (GetHeaderPackets().size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = GetHeaderPackets()[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet, check size against struct minus optional fields + if (packet.bytes < 1 + (signed)sizeof(SpeexHeader) - 2*(signed)sizeof(int)) { + return B_ERROR; + } + void * data = &(packet.packet[0]); + SpeexHeader * header = (SpeexHeader *)data; + + // get the format for the description + media_format_description description = speex_description(); + BMediaFormats formats; + result = formats.InitCheck(); + if (result == B_OK) { + result = formats.GetFormatFor(description, format); + } + if (result != B_OK) { + *format = speex_encoded_media_format(); + // ignore error, allow user to use ReadChunk interface + } + + // fill out format from header packet + if (header->bitrate > 0) { + format->u.encoded_audio.bit_rate = header->bitrate; + } else { + // TODO: manually compute it where possible + } + if (header->nb_channels == 1) { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT; + } else { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT; + } + fFrameRate = format->u.encoded_audio.output.frame_rate = header->rate; + format->u.encoded_audio.output.channel_count = header->nb_channels; + // allocate buffer, round up to nearest speex output_length size + int buffer_size = AudioBufferSize(&format->u.encoded_audio.output); + int output_length = header->frame_size * header->nb_channels * + (format->u.encoded_audio.output.format & 0xf); + buffer_size = ((buffer_size - 1) / output_length + 1) * output_length; + format->u.encoded_audio.output.buffer_size = buffer_size; + + // get comment packet + if (GetHeaderPackets().size() < 2) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + // get extra headers + while ((signed)GetHeaderPackets().size() < header->extra_headers) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); + + // TODO: count the frames in the first page.. somehow.. :-/ + int64 frames = 0; + + ogg_page page; + // read the first page + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 fFirstGranulepos = ogg_page_granulepos(&page); + TRACE("OggVorbisSeekable::GetStreamInfo: first granulepos: %lld\n", fFirstGranulepos); + // read our last page + off_t last = inherited::Seek(GetLastPagePosition(), SEEK_SET); + if (last < 0) { + return last; + } + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 last_granulepos = ogg_page_granulepos(&page); + + // seek back to the start + int64 frame = 0; + bigtime_t time = 0; + result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time); + if (result != B_OK) { + return result; + } + + // compute frame count and duration from sample count + frames = last_granulepos - fFirstGranulepos; + + *frameCount = frames; + *duration = (1000000LL * frames) / (long long)fFrameRate; + + return B_OK; +} + + +status_t +OggSpeexSeekable::GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader) +{ + status_t result = inherited::GetNextChunk(chunkBuffer, chunkSize, mediaHeader); + if (result != B_OK) { + TRACE("OggSpeexSeekable::GetNextChunk failed: GetNextChunk = %s\n", strerror(result)); + return result; + } + *chunkSize = ((ogg_packet*)*chunkBuffer)->bytes; + *chunkBuffer = ((ogg_packet*)*chunkBuffer)->packet; + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggSpeexSeekable.h b/src/add-ons/media/plugins/ogg/OggSpeexSeekable.h new file mode 100644 index 0000000000..a1a6065a1c --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggSpeexSeekable.h @@ -0,0 +1,28 @@ +#ifndef _OGG_SPEEX_SEEKABLE_H +#define _OGG_SPEEX_SEEKABLE_H + +#include "OggSeekable.h" + +namespace BPrivate { namespace media { + +class OggSpeexSeekable : public OggSeekable { +private: + typedef OggSeekable inherited; +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggSpeexSeekable(long serialno); + virtual ~OggSpeexSeekable(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); + virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader); + +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_SPEEX_SEEKABLE_H diff --git a/src/add-ons/media/plugins/ogg/OggSpeexStream.cpp b/src/add-ons/media/plugins/ogg/OggSpeexStream.cpp new file mode 100644 index 0000000000..1d7d97aa86 --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggSpeexStream.cpp @@ -0,0 +1,161 @@ +#include "OggSpeexFormats.h" +#include "OggSpeexStream.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * speex header from libspeex/speex_header.h + * also documented at http://www.speex.org/manual/node7.html#SECTION00073000000000000000 + */ + +typedef struct SpeexHeader { + char speex_string[8]; /**< Identifies a Speex bit-stream, always set to "Speex " */ + char speex_version[20]; /**< Speex version */ + int speex_version_id; /**< Version for Speex (for checking compatibility) */ + int header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ + int rate; /**< Sampling rate used */ + int mode; /**< Mode used (0 for narrowband, 1 for wideband) */ + int mode_bitstream_version; /**< Version ID of the bit-stream */ + int nb_channels; /**< Number of channels encoded */ + int bitrate; /**< Bit-rate used */ + int frame_size; /**< Size of frames */ + int vbr; /**< 1 for a VBR encoding, 0 otherwise */ + int frames_per_packet; /**< Number of frames stored per Ogg packet */ + int extra_headers; /**< Number of additional headers after the comments */ + int reserved1; /**< Reserved for future use, must be zero */ + int reserved2; /**< Reserved for future use, must be zero */ +} SpeexHeader; + + +/* + * OggSpeexStream implementations + */ + +/* static */ bool +OggSpeexStream::IsValidHeader(const ogg_packet & packet) +{ + return findIdentifier(packet,"Speex ",0); +} + +OggSpeexStream::OggSpeexStream(long serialno) + : OggStream(serialno) +{ + TRACE("OggSpeexStream::OggSpeexStream\n"); +} + +OggSpeexStream::~OggSpeexStream() +{ + +} + +status_t +OggSpeexStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggSpeexStream::GetStreamInfo\n"); + status_t result = B_OK; + ogg_packet packet; + + // get header packet + if (GetHeaderPackets().size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = GetHeaderPackets()[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet, check size against struct minus optional fields + if (packet.bytes < 1 + (signed)sizeof(SpeexHeader) - 2*(signed)sizeof(int)) { + return B_ERROR; + } + void * data = &(packet.packet[0]); + SpeexHeader * header = (SpeexHeader *)data; + + // get the format for the description + media_format_description description = speex_description(); + BMediaFormats formats; + result = formats.InitCheck(); + if (result == B_OK) { + result = formats.GetFormatFor(description, format); + } + if (result != B_OK) { + *format = speex_encoded_media_format(); + // ignore error, allow user to use ReadChunk interface + } + + // fill out format from header packet + if (header->bitrate > 0) { + format->u.encoded_audio.bit_rate = header->bitrate; + } else { + // TODO: manually compute it where possible + } + if (header->nb_channels == 1) { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT; + } else { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT; + } + format->u.encoded_audio.output.frame_rate = header->rate; + format->u.encoded_audio.output.channel_count = header->nb_channels; + // allocate buffer, round up to nearest speex output_length size + int buffer_size = AudioBufferSize(&format->u.encoded_audio.output); + int output_length = header->frame_size * header->nb_channels * + (format->u.encoded_audio.output.format & 0xf); + buffer_size = ((buffer_size - 1) / output_length + 1) * output_length; + format->u.encoded_audio.output.buffer_size = buffer_size; + + // get comment packet + if (GetHeaderPackets().size() < 2) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + // get extra headers + while ((signed)GetHeaderPackets().size() < header->extra_headers) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); + *duration = 100000000; + *frameCount = 60000; + return B_OK; +} + + +status_t +OggSpeexStream::GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader) +{ + status_t result = GetPacket(&fChunkPacket); + if (result != B_OK) { + TRACE("OggSpeexStream::GetNextChunk failed: GetPacket = %s\n", strerror(result)); + return result; + } + *chunkBuffer = fChunkPacket.packet; + *chunkSize = fChunkPacket.bytes; + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggSpeexStream.h b/src/add-ons/media/plugins/ogg/OggSpeexStream.h new file mode 100644 index 0000000000..e50ccf399d --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggSpeexStream.h @@ -0,0 +1,26 @@ +#ifndef _OGG_SPEEX_STREAM_H +#define _OGG_SPEEX_STREAM_H + +#include "OggStream.h" + +namespace BPrivate { namespace media { + +class OggSpeexStream : public OggStream { +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggSpeexStream(long serialno); + virtual ~OggSpeexStream(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); + virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader); + +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_SPEEX_STREAM_H diff --git a/src/add-ons/media/plugins/ogg/OggTheoraStream.cpp b/src/add-ons/media/plugins/ogg/OggTheoraStream.cpp new file mode 100644 index 0000000000..ebc3bc3649 --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTheoraStream.cpp @@ -0,0 +1,206 @@ +#include "OggTheoraFormats.h" +#include "OggTheoraStream.h" +#include +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * theora header parsing code from theora/theara.h + */ + +typedef enum { + OC_CS_UNSPECIFIED, + OC_CS_ITU_REC_470M, + OC_CS_ITU_REC_470BG, +} theora_colorspace; + +typedef struct { + ogg_uint32_t width; + ogg_uint32_t height; + ogg_uint32_t frame_width; + ogg_uint32_t frame_height; + ogg_uint32_t offset_x; + ogg_uint32_t offset_y; + ogg_uint32_t fps_numerator; + ogg_uint32_t fps_denominator; + ogg_uint32_t aspect_numerator; + ogg_uint32_t aspect_denominator; + theora_colorspace colorspace; + int target_bitrate; + int quality; + int quick_p; /* quick encode/decode */ + + /* decode only */ + unsigned char version_major; + unsigned char version_minor; + unsigned char version_subminor; + + void *codec_setup; + + /* encode only */ + int dropframes_p; + int keyframe_auto_p; + ogg_uint32_t keyframe_frequency; + ogg_uint32_t keyframe_frequency_force; /* also used for decode init to + get granpos shift correct */ + ogg_uint32_t keyframe_data_target_bitrate; + ogg_int32_t keyframe_auto_threshold; + ogg_uint32_t keyframe_mindistance; + ogg_int32_t noise_sensitivity; + ogg_int32_t sharpness; + +} theora_info; + +// based on theora/lib/toplevel.c _theora_unpack_info + +#define theora_read(x,y,z) ( *z = oggpack_read(x,y) ) + +#define OC_BADHEADER -1 + +static int _theora_unpack_info(theora_info *ci, oggpack_buffer *opb){ + long ret; + + theora_read(opb,8,&ret); + ci->version_major=(unsigned char)ret; + theora_read(opb,8,&ret); + ci->version_minor=(unsigned char)ret; + theora_read(opb,8,&ret); + ci->version_subminor=(unsigned char)ret; + +// if(ci->version_major!=VERSION_MAJOR)return(OC_VERSION); +// if(ci->version_minor>VERSION_MINOR)return(OC_VERSION); + + theora_read(opb,16,&ret); + ci->width=ret<<4; + theora_read(opb,16,&ret); + ci->height=ret<<4; + theora_read(opb,24,&ret); + ci->frame_width=ret; + theora_read(opb,24,&ret); + ci->frame_height=ret; + theora_read(opb,8,&ret); + ci->offset_x=ret; + theora_read(opb,8,&ret); + ci->offset_y=ret; + + theora_read(opb,32,&ret); + ci->fps_numerator=ret; + theora_read(opb,32,&ret); + ci->fps_denominator=ret; + theora_read(opb,24,&ret); + ci->aspect_numerator=ret; + theora_read(opb,24,&ret); + ci->aspect_denominator=ret; + + theora_read(opb,8,&ret); + ci->colorspace=(theora_colorspace)ret; + theora_read(opb,24,&ret); + ci->target_bitrate=ret; + theora_read(opb,6,&ret); + ci->quality=ret=ret; + + theora_read(opb,5,&ret); + ci->keyframe_frequency_force=1<u.encoded_video.frame_size = info.frame_width * info.frame_height ; + format->u.encoded_video.output.display.line_width = info.frame_width; + format->u.encoded_video.output.display.line_count = info.frame_height; + // TODO: wring more info out of the headers + + format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); + *duration = 80000000; + *frameCount = 60000; + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggTheoraStream.h b/src/add-ons/media/plugins/ogg/OggTheoraStream.h new file mode 100644 index 0000000000..338fec39ef --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTheoraStream.h @@ -0,0 +1,23 @@ +#ifndef _OGG_THEORA_STREAM_H +#define _OGG_THEORA_STREAM_H + +#include "OggStream.h" + +namespace BPrivate { namespace media { + +class OggTheoraStream : public OggStream { +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggTheoraStream(long serialno); + virtual ~OggTheoraStream(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_THEORA_STREAM_H diff --git a/src/add-ons/media/plugins/ogg/OggTobiasSeekable.cpp b/src/add-ons/media/plugins/ogg/OggTobiasSeekable.cpp new file mode 100644 index 0000000000..cf40cec64b --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTobiasSeekable.cpp @@ -0,0 +1,287 @@ +#include "OggTobiasFormats.h" +#include "OggTobiasSeekable.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * tobias header structs from http://tobias.everwicked.com/packfmt.htm + */ + +typedef struct tobias_stream_header_video +{ + ogg_int32_t width; + ogg_int32_t height; +} tobias_stream_header_video; + +typedef struct tobias_stream_header_audio +{ + ogg_int16_t channels; + ogg_int16_t blockalign; + ogg_int32_t avgbytespersec; +} tobias_stream_header_audio; + +typedef struct tobias_stream_header +{ + char streamtype[8]; + char subtype[4]; + + ogg_int32_t size; // size of the structure + + ogg_int64_t time_unit; // in reference time (100 ns units) + ogg_int64_t samples_per_unit; + ogg_int32_t default_len; // in media time + + ogg_int32_t buffersize; + ogg_int16_t bits_per_sample; + + union { + // Video specific + tobias_stream_header_video video; + // Audio specific + tobias_stream_header_audio audio; + }; +} tobias_stream_header; + +/* + * OggTobiasSeekable implementations + */ + +/* static */ bool +OggTobiasSeekable::IsValidHeader(const ogg_packet & packet) +{ + return findIdentifier(packet,"video",1) + || findIdentifier(packet,"audio",1) + || findIdentifier(packet,"text",1); +} + + +OggTobiasSeekable::OggTobiasSeekable(long serialno) + : OggSeekable(serialno) +{ + TRACE("OggTobiasSeekable::OggTobiasSeekable\n"); + fMicrosecPerFrame = 0; +} + + +OggTobiasSeekable::~OggTobiasSeekable() +{ + TRACE("OggTobiasSeekable::~OggTobiasSeekable\n"); +} + + +static status_t +get_video_format(tobias_stream_header * header, media_format * format) +{ + TRACE(" get_video_format\n"); + // get the format for the description + media_format_description description = tobias_video_description(); + description.u.avi.codec = header->subtype[3] << 24 | header->subtype[2] << 16 + | header->subtype[1] << 8 | header->subtype[0]; + BMediaFormats formats; + status_t result = formats.InitCheck(); + if (result == B_OK) { + result = formats.GetFormatFor(description, format); + } + if (result != B_OK) { + *format = tobias_video_encoded_media_format(); + // ignore error, allow user to use ReadChunk interface + } + + // fill out format from header packet + format->user_data_type = B_CODEC_TYPE_INFO; + strncpy((char*)format->user_data, header->subtype, 4); + format->u.encoded_video.frame_size + = header->video.width * header->video.height; + format->u.encoded_video.output.field_rate = 10000000.0 / header->time_unit; + format->u.encoded_video.output.interlace = 1; + format->u.encoded_video.output.first_active = 0; + format->u.encoded_video.output.last_active = header->video.height - 1; + format->u.encoded_video.output.orientation = B_VIDEO_TOP_LEFT_RIGHT; + format->u.encoded_video.output.pixel_width_aspect = 1; + format->u.encoded_video.output.pixel_height_aspect = 1; + format->u.encoded_video.output.display.line_width = header->video.width; + format->u.encoded_video.output.display.line_count = header->video.height; + format->u.encoded_video.output.display.bytes_per_row = 0; + format->u.encoded_video.output.display.pixel_offset = 0; + format->u.encoded_video.output.display.line_offset = 0; + format->u.encoded_video.output.display.flags = 0; + + // TODO: wring more info out of the headers + return B_OK; +} + + +static status_t +get_audio_format(tobias_stream_header * header, media_format * format) +{ + TRACE(" get_audio_format\n"); + debugger("get_audio_format"); + return B_UNSUPPORTED; +} + + +static status_t +get_text_format(tobias_stream_header * header, media_format * format) +{ + TRACE(" get_text_format\n"); + // get the format for the description + media_format_description description = tobias_text_description(); + BMediaFormats formats; + status_t result = formats.InitCheck(); + if (result == B_OK) { + result = formats.GetFormatFor(description, format); + } + if (result != B_OK) { + *format = tobias_text_encoded_media_format(); + // ignore error, allow user to use ReadChunk interface + } + + // fill out format from header packet + + return B_OK; +} + + +status_t +OggTobiasSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggTobiasSeekable::GetStreamInfo\n"); + status_t result = B_OK; + ogg_packet packet; + + // get header packet + if (GetHeaderPackets().size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = GetHeaderPackets()[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet + if (packet.bytes < 1+(signed)sizeof(tobias_stream_header)) { + return B_ERROR; + } + void * data = &(packet.packet[1]); + tobias_stream_header * header = (tobias_stream_header *)data; + + if (strcmp(header->streamtype, "video") == 0) { + result = get_video_format(header, format); + if (result != B_OK) { + return result; + } + } else if (strcmp(header->streamtype, "audio") == 0) { + result = get_audio_format(header, format); + if (result != B_OK) { + return result; + } + } else if (strcmp(header->streamtype, "text") == 0) { + result = get_text_format(header, format); + if (result != B_OK) { + return result; + } + } else { + *frameCount = 0; + // unknown streamtype + return B_BAD_VALUE; + } + + // get comment packet + if (GetHeaderPackets().size() < 2) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); + fMediaFormat = *format; + fMicrosecPerFrame = header->time_unit / 10.0; + fFrameRate = 1000000.0 / fMicrosecPerFrame; + + // TODO: count the frames in the first page.. somehow.. :-/ + int64 frames = 0; + + ogg_page page; + // read the first page + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 fFirstGranulepos = ogg_page_granulepos(&page); + TRACE("OggVorbisSeekable::GetStreamInfo: first granulepos: %lld\n", fFirstGranulepos); + // read our last page + off_t last = inherited::Seek(GetLastPagePosition(), SEEK_SET); + if (last < 0) { + return last; + } + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 last_granulepos = ogg_page_granulepos(&page); + + // seek back to the start + int64 frame = 0; + bigtime_t time = 0; + result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time); + if (result != B_OK) { + return result; + } + + // compute frame count and duration from sample count + frames = last_granulepos - fFirstGranulepos; + + *frameCount = frames; + *duration = (1000000LL * frames) / (long long)fFrameRate; + return B_OK; +} + + +status_t +OggTobiasSeekable::GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader) +{ + status_t result = inherited::GetNextChunk(chunkBuffer, chunkSize, mediaHeader); + if (result != B_OK) { + TRACE("OggTobiasSeekable::GetNextChunk failed: GetNextChunk = %s\n", strerror(result)); + return result; + } + *chunkSize = ((ogg_packet*)*chunkBuffer)->bytes; + *chunkBuffer = ((ogg_packet*)*chunkBuffer)->packet; + bool keyframe = ((uint*)chunkBuffer)[0] & (1 << 3); // ?? + if (fMediaFormat.type == B_MEDIA_ENCODED_VIDEO) { + mediaHeader->type = fMediaFormat.type; + mediaHeader->u.encoded_video.field_flags = (keyframe ? B_MEDIA_KEY_FRAME : 0); + mediaHeader->u.encoded_video.first_active_line + = fMediaFormat.u.encoded_video.output.first_active; + mediaHeader->u.encoded_video.line_count + = fMediaFormat.u.encoded_video.output.display.line_count; + } + if (mediaHeader->start_time < 0) { + fCurrentFrame++; + fCurrentTime = (bigtime_t)((fCurrentFrame * 1000000LL) / fFrameRate); + mediaHeader->start_time = fCurrentTime; + } +// fprintf(stderr, "current frame = %lld, time = %lld\n", fCurrentFrame, fCurrentTime); + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggTobiasSeekable.h b/src/add-ons/media/plugins/ogg/OggTobiasSeekable.h new file mode 100644 index 0000000000..e7d2a83e7a --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTobiasSeekable.h @@ -0,0 +1,31 @@ +#ifndef _OGG_TOBIAS_SEEKABLE_H +#define _OGG_TOBIAS_SEEKABLE_H + +#include "OggSeekable.h" + +namespace BPrivate { namespace media { + +class OggTobiasSeekable : public OggSeekable { +private: + typedef OggSeekable inherited; +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggTobiasSeekable(long serialno); + virtual ~OggTobiasSeekable(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); + virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader); + +private: + media_format fMediaFormat; + double fMicrosecPerFrame; +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_TOBIAS_SEEKABLE_H diff --git a/src/add-ons/media/plugins/ogg/OggTobiasStream.cpp b/src/add-ons/media/plugins/ogg/OggTobiasStream.cpp new file mode 100644 index 0000000000..9175c91f0d --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTobiasStream.cpp @@ -0,0 +1,266 @@ +#include "OggTobiasFormats.h" +#include "OggTobiasStream.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * tobias header structs from http://tobias.everwicked.com/packfmt.htm + */ + +typedef struct tobias_stream_header_video +{ + ogg_int32_t width; + ogg_int32_t height; +} tobias_stream_header_video; + +typedef struct tobias_stream_header_audio +{ + ogg_int16_t channels; + ogg_int16_t blockalign; + ogg_int32_t avgbytespersec; +} tobias_stream_header_audio; + +typedef struct tobias_stream_header +{ + char streamtype[8]; + char subtype[4]; + + ogg_int32_t size; // size of the structure + + ogg_int64_t time_unit; // in reference time (100 ns units) + ogg_int64_t samples_per_unit; + ogg_int32_t default_len; // in media time + + ogg_int32_t buffersize; + ogg_int16_t bits_per_sample; + + union { + // Video specific + tobias_stream_header_video video; + // Audio specific + tobias_stream_header_audio audio; + }; +} tobias_stream_header; + +/* + * OggTobiasStream implementations + */ + +/* static */ bool +OggTobiasStream::IsValidHeader(const ogg_packet & packet) +{ + return findIdentifier(packet,"video",1) + || findIdentifier(packet,"audio",1) + || findIdentifier(packet,"text",1); +} + + +OggTobiasStream::OggTobiasStream(long serialno) + : OggStream(serialno) +{ + TRACE("OggTobiasStream::OggTobiasStream\n"); + fMicrosecPerFrame = 0; +} + + +OggTobiasStream::~OggTobiasStream() +{ + TRACE("OggTobiasStream::~OggTobiasStream\n"); +} + + +static status_t +get_video_format(tobias_stream_header * header, media_format * format) +{ + TRACE(" get_video_format\n"); + // get the format for the description + media_format_description description = tobias_video_description(); + description.u.avi.codec = header->subtype[3] << 24 | header->subtype[2] << 16 + | header->subtype[1] << 8 | header->subtype[0]; + BMediaFormats formats; + status_t result = formats.InitCheck(); + if (result == B_OK) { + result = formats.GetFormatFor(description, format); + } + if (result != B_OK) { + *format = tobias_video_encoded_media_format(); + // ignore error, allow user to use ReadChunk interface + } + + // fill out format from header packet + format->user_data_type = B_CODEC_TYPE_INFO; + strncpy((char*)format->user_data, header->subtype, 4); + format->u.encoded_video.frame_size + = header->video.width * header->video.height; + format->u.encoded_video.output.field_rate = 10000000.0 / header->time_unit; + format->u.encoded_video.output.interlace = 1; + format->u.encoded_video.output.first_active = 0; + format->u.encoded_video.output.last_active = header->video.height - 1; + format->u.encoded_video.output.orientation = B_VIDEO_TOP_LEFT_RIGHT; + format->u.encoded_video.output.pixel_width_aspect = 1; + format->u.encoded_video.output.pixel_height_aspect = 1; + format->u.encoded_video.output.display.line_width = header->video.width; + format->u.encoded_video.output.display.line_count = header->video.height; + format->u.encoded_video.output.display.bytes_per_row = 0; + format->u.encoded_video.output.display.pixel_offset = 0; + format->u.encoded_video.output.display.line_offset = 0; + format->u.encoded_video.output.display.flags = 0; + + // TODO: wring more info out of the headers + return B_OK; +} + + +static status_t +get_audio_format(tobias_stream_header * header, media_format * format) +{ + TRACE(" get_audio_format\n"); + debugger("get_audio_format"); + return B_UNSUPPORTED; +} + + +static status_t +get_text_format(tobias_stream_header * header, media_format * format) +{ + TRACE(" get_text_format\n"); + // get the format for the description + media_format_description description = tobias_text_description(); + BMediaFormats formats; + status_t result = formats.InitCheck(); + if (result == B_OK) { + result = formats.GetFormatFor(description, format); + } + if (result != B_OK) { + *format = tobias_text_encoded_media_format(); + // ignore error, allow user to use ReadChunk interface + } + + // fill out format from header packet + + return B_OK; +} + + +status_t +OggTobiasStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggTobiasStream::GetStreamInfo\n"); + status_t result = B_OK; + ogg_packet packet; + + // get header packet + if (GetHeaderPackets().size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = GetHeaderPackets()[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet + if (packet.bytes < 1+(signed)sizeof(tobias_stream_header)) { + return B_ERROR; + } + void * data = &(packet.packet[1]); + tobias_stream_header * header = (tobias_stream_header *)data; + + if (strcmp(header->streamtype, "video") == 0) { + result = get_video_format(header, format); + if (result != B_OK) { + return result; + } + *frameCount = (bigtime_t)(3 * 3600 * format->u.encoded_video.output.field_rate); + } else if (strcmp(header->streamtype, "audio") == 0) { + result = get_audio_format(header, format); + if (result != B_OK) { + return result; + } + *frameCount = 2000000; + } else if (strcmp(header->streamtype, "text") == 0) { + result = get_text_format(header, format); + if (result != B_OK) { + return result; + } + *frameCount = 2000000; + } else { + *frameCount = 0; + // unknown streamtype + return B_BAD_VALUE; + } + + // get comment packet + if (GetHeaderPackets().size() < 2) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); + fMediaFormat = *format; + fMicrosecPerFrame = header->time_unit / 10.0; + *duration = (bigtime_t)(*frameCount * fMicrosecPerFrame); + return B_OK; +} + + +status_t +OggTobiasStream::GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader) +{ + status_t result = GetPacket(&fChunkPacket); + if (result != B_OK) { + TRACE("OggTobiasStream::GetNextChunk failed: GetPacket = %s\n", strerror(result)); + return result; + } + *chunkBuffer = fChunkPacket.packet; + *chunkSize = fChunkPacket.bytes; + bool keyframe = fChunkPacket.packet[0] & (1 << 3); // ?? + if (fMediaFormat.type == B_MEDIA_ENCODED_VIDEO) { + mediaHeader->type = fMediaFormat.type; + mediaHeader->start_time = fCurrentTime; + mediaHeader->u.encoded_video.field_flags = (keyframe ? B_MEDIA_KEY_FRAME : 0); + mediaHeader->u.encoded_video.first_active_line + = fMediaFormat.u.encoded_video.output.first_active; + mediaHeader->u.encoded_video.line_count + = fMediaFormat.u.encoded_video.output.display.line_count; + } + fCurrentFrame++; + fCurrentTime = (bigtime_t)(fCurrentFrame * fMicrosecPerFrame); + return B_OK; +} + + +status_t +OggTobiasStream::AddPage(off_t position, const ogg_page & page) +{ + status_t status = OggStream::AddPage(position, page); + if (fMediaFormat.type == B_MEDIA_HTML) { + ogg_packet packet; + GetPacket(&packet); + if (packet.bytes > 4) { + fprintf(stderr, "%s\n", &(packet.packet[3])); + } + } + return status; +} diff --git a/src/add-ons/media/plugins/ogg/OggTobiasStream.h b/src/add-ons/media/plugins/ogg/OggTobiasStream.h new file mode 100644 index 0000000000..c9e27d531e --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTobiasStream.h @@ -0,0 +1,32 @@ +#ifndef _OGG_TOBIAS_STREAM_H +#define _OGG_TOBIAS_STREAM_H + +#include "OggStream.h" + +namespace BPrivate { namespace media { + +class OggTobiasStream : public OggStream { +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggTobiasStream(long serialno); + virtual ~OggTobiasStream(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); + virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader); + + // reader push input function + virtual status_t AddPage(off_t position, const ogg_page & page); + +private: + media_format fMediaFormat; + double fMicrosecPerFrame; +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_TOBIAS_STREAM_H diff --git a/src/add-ons/media/plugins/ogg/OggVorbisSeekable.cpp b/src/add-ons/media/plugins/ogg/OggVorbisSeekable.cpp new file mode 100644 index 0000000000..d3928675cb --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggVorbisSeekable.cpp @@ -0,0 +1,230 @@ +#include "OggVorbisFormats.h" +#include "OggVorbisSeekable.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * vorbis header parsing code from libvorbis/info.c + */ + +typedef struct vorbis_info{ + int version; + int channels; + long rate; + + /* The below bitrate declarations are *hints*. + Combinations of the three values carry the following implications: + + all three set to the same value: + implies a fixed rate bitstream + only nominal set: + implies a VBR stream that averages the nominal bitrate. No hard + upper/lower limit + upper and or lower set: + implies a VBR bitstream that obeys the bitrate limits. nominal + may also be set to give a nominal rate. + none set: + the coder does not care to speculate. + */ + + long bitrate_upper; + long bitrate_nominal; + long bitrate_lower; + long bitrate_window; + + void *codec_setup; +} vorbis_info; + + +// based on libvorbis/info.c _vorbis_unpack_info +static int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb){ + vi->version = oggpack_read(opb, 32); + if (vi->version != 0) { + return -1; + } + vi->channels = oggpack_read(opb, 8); + vi->rate = oggpack_read(opb, 32); + vi->bitrate_upper = oggpack_read(opb, 32); + vi->bitrate_nominal = oggpack_read(opb, 32); + vi->bitrate_lower = oggpack_read(opb, 32); + long blocksizes0 = oggpack_read(opb, 4); + long blocksizes1 = oggpack_read(opb, 4); + if (vi->rate < 1) { + return -1; + } + if (vi->channels < 1) { + return -1; + } + if (blocksizes0 < 8) { + return -1; + } + if (blocksizes1 < blocksizes0) { + return -1; + } + if (oggpack_read(opb, 1) != 1) { + return -1; + } /* EOP check */ + return 0; +} + + +/* + * OggVorbisSeekable implementations + */ + +/* static */ bool +OggVorbisSeekable::IsValidHeader(const ogg_packet & packet) +{ + return findIdentifier(packet,"vorbis",1); +} + +OggVorbisSeekable::OggVorbisSeekable(long serialno) + : OggSeekable(serialno) +{ + TRACE("OggVorbisSeekable::OggVorbisSeekable\n"); +} + +OggVorbisSeekable::~OggVorbisSeekable() +{ + TRACE("OggVorbisSeekable::~OggVorbisSeekable\n"); +} + +status_t +OggVorbisSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggVorbisSeekable::GetStreamInfo\n"); + status_t result = B_OK; + ogg_packet packet; + + // get header packet + if (GetHeaderPackets().size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = GetHeaderPackets()[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet + // based on libvorbis/info.c vorbis_synthesis_headerin(...) + oggpack_buffer opb; + oggpack_readinit(&opb, packet.packet, packet.bytes); + int packtype = oggpack_read(&opb, 8); + if (packtype != 0x01) { + return B_ERROR; // first packet was not an info packet + } + // discard vorbis string + for (uint i = 0 ; i < sizeof("vorbis") - 1 ; i++) { + oggpack_read(&opb, 8); + } + vorbis_info info; + if (_vorbis_unpack_info(&info, &opb) != 0) { + return B_ERROR; // couldn't unpack info + } + + // get the format for the description + media_format_description description = vorbis_description(); + BMediaFormats formats; + result = formats.InitCheck(); + if (result == B_OK) { + result = formats.GetFormatFor(description, format); + } + if (result != B_OK) { + *format = vorbis_encoded_media_format(); + // ignore error, allow user to use ReadChunk interface + } + + // fill out format from header packet + if (info.bitrate_nominal > 0) { + format->u.encoded_audio.bit_rate = info.bitrate_nominal; + } else if (info.bitrate_upper > 0) { + format->u.encoded_audio.bit_rate = info.bitrate_upper; + } else if (info.bitrate_lower > 0) { + format->u.encoded_audio.bit_rate = info.bitrate_lower; + } + if (info.channels == 1) { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT; + } else { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT; + } + fFrameRate = format->u.encoded_audio.output.frame_rate = (float)info.rate; + format->u.encoded_audio.output.channel_count = info.channels; + format->u.encoded_audio.output.buffer_size + = AudioBufferSize(&format->u.encoded_audio.output); + + // get comment packet + if (GetHeaderPackets().size() < 2) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + // get codebook packet + if (GetHeaderPackets().size() < 3) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); + + // TODO: count the frames in the first page.. somehow.. :-/ + int64 frames = 0; + + ogg_page page; + // read the first page + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 fFirstGranulepos = ogg_page_granulepos(&page); + TRACE("OggVorbisSeekable::GetStreamInfo: first granulepos: %lld\n", fFirstGranulepos); + // read our last page + off_t last = inherited::Seek(GetLastPagePosition(), SEEK_SET); + if (last < 0) { + return last; + } + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 last_granulepos = ogg_page_granulepos(&page); + + // seek back to the start + int64 frame = 0; + bigtime_t time = 0; + result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time); + if (result != B_OK) { + return result; + } + + // compute frame count and duration from sample count + frames = last_granulepos - fFirstGranulepos; + + *frameCount = frames; + *duration = (1000000LL * frames) / (long long)fFrameRate; + + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggVorbisSeekable.h b/src/add-ons/media/plugins/ogg/OggVorbisSeekable.h new file mode 100644 index 0000000000..76081c71ef --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggVorbisSeekable.h @@ -0,0 +1,25 @@ +#ifndef _OGG_VORBIS_SEEKABLE_H +#define _OGG_VORBIS_SEEKABLE_H + +#include "OggSeekable.h" + +namespace BPrivate { namespace media { + +class OggVorbisSeekable : public OggSeekable { +private: + typedef OggSeekable inherited; +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggVorbisSeekable(long serialno); + virtual ~OggVorbisSeekable(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_VORBIS_SEEKABLE_H diff --git a/src/add-ons/media/plugins/ogg/OggVorbisStream.cpp b/src/add-ons/media/plugins/ogg/OggVorbisStream.cpp new file mode 100644 index 0000000000..7b190b16ff --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggVorbisStream.cpp @@ -0,0 +1,196 @@ +#include "OggVorbisFormats.h" +#include "OggVorbisStream.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * vorbis header parsing code from libvorbis/info.c + */ + +typedef struct vorbis_info{ + int version; + int channels; + long rate; + + /* The below bitrate declarations are *hints*. + Combinations of the three values carry the following implications: + + all three set to the same value: + implies a fixed rate bitstream + only nominal set: + implies a VBR stream that averages the nominal bitrate. No hard + upper/lower limit + upper and or lower set: + implies a VBR bitstream that obeys the bitrate limits. nominal + may also be set to give a nominal rate. + none set: + the coder does not care to speculate. + */ + + long bitrate_upper; + long bitrate_nominal; + long bitrate_lower; + long bitrate_window; + + void *codec_setup; +} vorbis_info; + +// based on libvorbis/info.c _vorbis_unpack_info +static int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb){ + vi->version = oggpack_read(opb, 32); + if (vi->version != 0) { + return -1; + } + vi->channels = oggpack_read(opb, 8); + vi->rate = oggpack_read(opb, 32); + vi->bitrate_upper = oggpack_read(opb, 32); + vi->bitrate_nominal = oggpack_read(opb, 32); + vi->bitrate_lower = oggpack_read(opb, 32); + long blocksizes0 = oggpack_read(opb, 4); + long blocksizes1 = oggpack_read(opb, 4); + if (vi->rate < 1) { + return -1; + } + if (vi->channels < 1) { + return -1; + } + if (blocksizes0 < 8) { + return -1; + } + if (blocksizes1 < blocksizes0) { + return -1; + } + if (oggpack_read(opb, 1) != 1) { + return -1; + } /* EOP check */ + return 0; +} + +/* + * OggVorbisStream implementations + */ + +/* static */ bool +OggVorbisStream::IsValidHeader(const ogg_packet & packet) +{ + return findIdentifier(packet,"vorbis",1); +} + +OggVorbisStream::OggVorbisStream(long serialno) + : OggStream(serialno) +{ + TRACE("OggVorbisStream::OggVorbisStream\n"); +} + +OggVorbisStream::~OggVorbisStream() +{ + TRACE("OggVorbisStream::~OggVorbisStream\n"); +} + +status_t +OggVorbisStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggVorbisStream::GetStreamInfo\n"); + status_t result = B_OK; + ogg_packet packet; + + // get header packet + if (GetHeaderPackets().size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = GetHeaderPackets()[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet + // based on libvorbis/info.c vorbis_synthesis_headerin(...) + oggpack_buffer opb; + oggpack_readinit(&opb, packet.packet, packet.bytes); + int packtype = oggpack_read(&opb, 8); + if (packtype != 0x01) { + return B_ERROR; // first packet was not an info packet + } + // discard vorbis string + for (uint i = 0 ; i < sizeof("vorbis") - 1 ; i++) { + oggpack_read(&opb, 8); + } + vorbis_info info; + if (_vorbis_unpack_info(&info, &opb) != 0) { + return B_ERROR; // couldn't unpack info + } + + // get the format for the description + media_format_description description = vorbis_description(); + BMediaFormats formats; + result = formats.InitCheck(); + if (result == B_OK) { + result = formats.GetFormatFor(description, format); + } + if (result != B_OK) { + *format = vorbis_encoded_media_format(); + // ignore error, allow user to use ReadChunk interface + } + + // fill out format from header packet + if (info.bitrate_nominal > 0) { + format->u.encoded_audio.bit_rate = info.bitrate_nominal; + } else if (info.bitrate_upper > 0) { + format->u.encoded_audio.bit_rate = info.bitrate_upper; + } else if (info.bitrate_lower > 0) { + format->u.encoded_audio.bit_rate = info.bitrate_lower; + } + if (info.channels == 1) { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT; + } else { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT; + } + format->u.encoded_audio.output.frame_rate = (float)info.rate; + format->u.encoded_audio.output.channel_count = info.channels; + format->u.encoded_audio.output.buffer_size + = AudioBufferSize(&format->u.encoded_audio.output); + + // get comment packet + if (GetHeaderPackets().size() < 2) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + // get codebook packet + if (GetHeaderPackets().size() < 3) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); + + // compute frame count and duration from sample count + *duration = 5 * 60 * 1000000; + *frameCount = *duration * (long long)format->u.encoded_audio.output.frame_rate; + + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggVorbisStream.h b/src/add-ons/media/plugins/ogg/OggVorbisStream.h new file mode 100644 index 0000000000..7d13c7712a --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggVorbisStream.h @@ -0,0 +1,23 @@ +#ifndef _OGG_VORBIS_STREAM_H +#define _OGG_VORBIS_STREAM_H + +#include "OggStream.h" + +namespace BPrivate { namespace media { + +class OggVorbisStream : public OggStream { +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggVorbisStream(long serialno); + virtual ~OggVorbisStream(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_VORBIS_STREAM_H