Skip to content
This repository has been archived by the owner on Jul 25, 2020. It is now read-only.

Commit

Permalink
Support Opus in Ogg container
Browse files Browse the repository at this point in the history
Now supported in both Ogg and WebM container.
Needs more testing! Probably doesn't handle preroll correctly.

Fixes #21
  • Loading branch information
bvibber committed Feb 2, 2018
1 parent c7a0bbf commit 823961c
Show file tree
Hide file tree
Showing 6 changed files with 482 additions and 16 deletions.
128 changes: 118 additions & 10 deletions Classes/OGVDecoderOgg.m
Expand Up @@ -22,6 +22,11 @@
#include <theora/theoradec.h>
#endif

#ifdef OGVKIT_HAVE_OPUS_DECODER
#include "libopus/opus_multistream.h"
#include "opus_header.h"
#endif

#include "skeleton.h"

#import "OGVDecoderOgg.h"
Expand Down Expand Up @@ -121,14 +126,22 @@ @implementation OGVDecoderOgg {
OGVVideoBuffer *queuedFrame;

/* Audio decode state */
int vorbis_p;
int vorbis_processing_headers;
#ifdef OGVKIT_HAVE_VORBIS_DECODER
int vorbis_processing_headers;
//ogg_stream_state vo;
vorbis_info vi;
vorbis_dsp_state vd;
vorbis_block vb;
vorbis_comment vc;
#endif
#ifdef OGVKIT_HAVE_OPUS_DECODER
int opus_processing_headers;

OpusMSDecoder *opusDecoder;
float* opusPcmNonInterleaved;
float* opusPcmInterleaved;
/* 120ms at 48000 */
#define OPUS_MAX_FRAME_SIZE (960*6)
#endif
OGVAudioBuffer *queuedAudio;

Expand Down Expand Up @@ -246,6 +259,34 @@ - (int)processBegin:(OGVDecoderOggPacket *)packet serialno:(long)serialno
}
#endif /* OGVKIT_HAVE_VORBIS_DECODER */

#ifdef OGVKIT_HAVE_OPUS_DECODER
if (!audioStream && content == OGGZ_CONTENT_OPUS) {
OpusHeader header;
if (opus_header_parse(packet.oggPacket->packet, (int)packet.oggPacket->bytes, &header)) {
int err;
opusDecoder = opus_multistream_decoder_create(48000, header.channels, header.nb_streams, header.nb_coupled, header.stream_map, &err);
if (err != OPUS_OK) {
[OGVKit.singleton.logger warnWithFormat:@"error %d in opus setup; skipping track", err];
} else {
opusPcmNonInterleaved = (float*) malloc(sizeof(float) * header.channels * OPUS_MAX_FRAME_SIZE);
opusPcmInterleaved = (float*) malloc(sizeof(float) * header.channels * OPUS_MAX_FRAME_SIZE);

opus_multistream_decoder_ctl(opusDecoder, OPUS_SET_GAIN(header.gain));

audioCodec = content;
audioStream = serialno;

self.audioFormat = [[OGVAudioFormat alloc] initWithChannels:header.channels
sampleRate:48000];

opus_processing_headers = 1;
}
} else {
[OGVKit.singleton.logger warnWithFormat:@"invalid Opus header, skipping track"];
}
}
#endif

if (!skeletonStream && content == OGGZ_CONTENT_SKELETON) {
skeletonStream = serialno;

Expand Down Expand Up @@ -333,6 +374,19 @@ - (int) processHeaders:(OGVDecoderOggPacket *)packet serialno:(long)serialno
}
#endif /* OGVKIT_HAVE_VORBIS_DECODER */

#ifdef OGVKIT_HAVE_OPUS_DECODER
if (audioCodec == OGGZ_CONTENT_OPUS) {
if (opus_processing_headers == 1) {
opus_processing_headers++;
// skip over comment header
[OGVKit.singleton.logger debugWithFormat:@"skipping opus comment header"];
audioHeadersComplete = YES;
} else {
[OGVKit.singleton.logger warnWithFormat:@"Unexpected opus header count %d", opus_processing_headers];
}
}
#endif /* OGVKIT_HAVE_OPUS_DECODER */

}
}

Expand Down Expand Up @@ -552,6 +606,41 @@ - (BOOL)decodeAudio
}
#endif /* OGVKIT_HAVE_VORBIS_DECODER */

#ifdef OGVKIT_HAVE_OPUS_DECODER
if (audioCodec == OGGZ_CONTENT_OPUS) {
int sampleCount = opus_multistream_decode_float(opusDecoder,
packet.oggPacket->packet,
(int)packet.oggPacket->bytes,
opusPcmInterleaved,
OPUS_MAX_FRAME_SIZE,
0);
if (sampleCount > 0) {
int opusChannels = self.audioFormat.channels;
for (int channel = 0; channel < opusChannels; channel++) {
for (int sample = 0; sample < sampleCount; sample++) {
opusPcmNonInterleaved[channel * sampleCount + sample] = opusPcmInterleaved[channel + opusChannels * sample];
}
}

float* pcmJagged[opusChannels];
for (int i = 0; i < opusChannels; i++) {
pcmJagged[i] = &(opusPcmNonInterleaved[i * sampleCount]);
}

ogg_int64_t audiobuf_granulepos = packet.oggzPacket->pos.calc_granulepos;
float audiobuf_time = (double)audiobuf_granulepos / 48000.0;

queuedAudio = [[OGVAudioBuffer alloc] initWithPCM:pcmJagged samples:sampleCount format:self.audioFormat timestamp:audiobuf_time];

return YES;
} else {
[OGVKit.singleton.logger errorWithFormat:@"OPUS decoder gave an empty packet!"];
return NO;
}

}
#endif /* OGVKIT_HAVE_OPUS_DECODER */

return NO;
}

Expand Down Expand Up @@ -648,7 +737,20 @@ - (void)dealloc
vorbis_comment_clear(&vc);
vorbis_info_clear(&vi);
#endif

#ifdef OGVKIT_HAVE_OPUS_DECODER
if (audioCodec == OGGZ_CONTENT_OPUS) {
if (opusDecoder) {
opus_multistream_decoder_destroy(opusDecoder);
}
if (opusPcmInterleaved) {
free(opusPcmInterleaved);
}
if (opusPcmNonInterleaved) {
free(opusPcmNonInterleaved);
}
}
#endif

oggskel_destroy(skeleton);

oggz_close(oggz);
Expand Down Expand Up @@ -887,14 +989,14 @@ - (BOOL)audioReady

- (float)audioTimestamp
{
#ifdef OGVKIT_HAVE_VORBIS_DECODER
OGVDecoderOggPacket *packet = [audioPackets peek];
if (packet) {
ogg_int64_t audiobuf_granulepos = packet.oggzPacket->pos.calc_granulepos;
float audiobuf_time = vorbis_granule_time(&vd, audiobuf_granulepos);
return audiobuf_time;
if (self.audioFormat) {
OGVDecoderOggPacket *packet = [audioPackets peek];
if (packet) {
ogg_int64_t audiobuf_granulepos = packet.oggzPacket->pos.calc_granulepos;
float audiobuf_time = (float)audiobuf_granulepos / self.audioFormat.sampleRate;
return audiobuf_time;
}
}
#endif
return -1;
}

Expand Down Expand Up @@ -935,6 +1037,12 @@ + (BOOL)canPlayType:(OGVMediaType *)mediaType
knownCodecs++;
continue;
}
#endif
#ifdef OGVKIT_HAVE_OPUS_DECODER
if ([codec isEqualToString:@"opus"]) {
knownCodecs++;
continue;
}
#endif
unknownCodecs++;
}
Expand Down
10 changes: 8 additions & 2 deletions Example/OGVKit Example/OGVCommonsExampleItem.m
Expand Up @@ -34,13 +34,19 @@ -(NSArray *)formats
if (![formats containsObject:dformat]) {
[formats addObject:dformat];
}
} else {
[formats addObject:derivative[@"transcodekey"]];
}
}
}

// hack for audio files for now
if ([formats count] == 0) {
[formats addObject:@"ogg"];
NSString *ext = [[self.filename pathExtension] lowercaseString];
if ([ext isEqualToString:@"oga"]) {
ext = @"ogg";
}
if (![formats containsObject:ext]) {
[formats addObject:ext];
}
return formats;
}
Expand Down
13 changes: 11 additions & 2 deletions Example/OGVKit Example/OGVViewController.m
Expand Up @@ -87,6 +87,8 @@ - (void)viewDidLoad
filename:@"Bach_C_Major_Prelude_Werckmeister.ogg"],
[[OGVCommonsExampleItem alloc] initWithTitle:@"Arigato (short audio)"
filename:@"Ja-arigato.oga"],
[[OGVCommonsExampleItem alloc] initWithTitle:@"O du froehliche (Opus audio)"
filename:@"O du froehliche - GL 238 audio.opus"],

// Local test files
[[OGVLinkedExampleItem alloc] initWithTitle:@"Res switching (local)"
Expand Down Expand Up @@ -182,11 +184,18 @@ - (void)updateFormats
{
formats = [source formats];
if (![formats containsObject:format]) {
format = formats[0];
if ([formats containsObject:@"ogg"]) {
// prefer ogg over mp3
format = @"ogg";
}
if ([formats containsObject:@"opus"]) {
// prefer opus over ogg
format = @"opus";
}
if ([formats containsObject:@"webm"]) {
// prefer webm over ogv
format = @"webm";
} else {
format = formats[0];
}
}

Expand Down
10 changes: 8 additions & 2 deletions OGVKit.podspec
Expand Up @@ -83,6 +83,10 @@ Pod::Spec.new do |s|
soggvorbis.dependency 'OGVKit/OggDemuxer'
soggvorbis.dependency 'OGVKit/VorbisDecoder'
end
sogg.subspec "Opus" do |soggopus|
soggopus.dependency 'OGVKit/OggDemuxer'
soggopus.dependency 'OGVKit/OpusDecoder'
end
end
s.subspec "WebM" do |swebm|
swebm.subspec "VP8" do |swebmvp8|
Expand Down Expand Up @@ -113,7 +117,7 @@ Pod::Spec.new do |s|
soggdemuxer.private_header_files = "Classes/OGVDecoderOgg.h",
"Classes/OGVDecoderOggPacket.h"
soggdemuxer.dependency 'OGVKit/Core'
soggdemuxer.dependency 'liboggz'
soggdemuxer.dependency 'liboggz', '1.2.0-1'
soggdemuxer.dependency 'OGVKit/libskeleton', '~>0.4'
end
s.subspec "WebMDemuxer" do |swebmdemuxer|
Expand All @@ -129,7 +133,7 @@ Pod::Spec.new do |s|
s.subspec "TheoraDecoder" do |stheoradecoder|
stheoradecoder.xcconfig = { 'OTHER_CFLAGS' => '-DOGVKIT_HAVE_THEORA_DECODER' }
stheoradecoder.dependency 'OGVKit/Core'
stheoradecoder.dependency 'libtheora', '1.2.0-1'
stheoradecoder.dependency 'libtheora', '1.2.0-3'
end
s.subspec "VP8Decoder" do |svp8decoder|
svp8decoder.xcconfig = { 'OTHER_CFLAGS' => '-DOGVKIT_HAVE_VP8_DECODER' }
Expand All @@ -147,6 +151,8 @@ Pod::Spec.new do |s|
sopusdecoder.xcconfig = { 'OTHER_CFLAGS' => '-DOGVKIT_HAVE_OPUS_DECODER' }
sopusdecoder.dependency 'OGVKit/Core'
sopusdecoder.dependency 'libopus'
sopusdecoder.source_files = "opus-tools/src/opus_header.h",
"opus-tools/src/opus_header.c"
end

# AVFoundation-backed playback for MP4, MP3
Expand Down

0 comments on commit 823961c

Please sign in to comment.