diff options
Diffstat (limited to 'src/decoder/OpusDecoderPlugin.cxx')
-rw-r--r-- | src/decoder/OpusDecoderPlugin.cxx | 485 |
1 files changed, 0 insertions, 485 deletions
diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx deleted file mode 100644 index 93b043065..000000000 --- a/src/decoder/OpusDecoderPlugin.cxx +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Copyright (C) 2003-2014 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "OpusDecoderPlugin.h" -#include "OpusDomain.hxx" -#include "OpusHead.hxx" -#include "OpusTags.hxx" -#include "OggFind.hxx" -#include "OggSyncState.hxx" -#include "DecoderAPI.hxx" -#include "OggCodec.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagBuilder.hxx" -#include "InputStream.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <opus.h> -#include <ogg/ogg.h> - -#include <glib.h> - -#include <string.h> - -static constexpr opus_int32 opus_sample_rate = 48000; - -gcc_pure -static bool -IsOpusHead(const ogg_packet &packet) -{ - return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0; -} - -gcc_pure -static bool -IsOpusTags(const ogg_packet &packet) -{ - return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0; -} - -static bool -mpd_opus_init(gcc_unused const config_param ¶m) -{ - LogDebug(opus_domain, opus_get_version_string()); - - return true; -} - -class MPDOpusDecoder { - Decoder &decoder; - InputStream &input_stream; - - ogg_stream_state os; - - OpusDecoder *opus_decoder; - opus_int16 *output_buffer; - unsigned output_size; - - bool os_initialized; - bool found_opus; - - int opus_serialno; - - ogg_int64_t eos_granulepos; - - size_t frame_size; - -public: - MPDOpusDecoder(Decoder &_decoder, - InputStream &_input_stream) - :decoder(_decoder), input_stream(_input_stream), - opus_decoder(nullptr), - output_buffer(nullptr), output_size(0), - os_initialized(false), found_opus(false) {} - ~MPDOpusDecoder(); - - bool ReadFirstPage(OggSyncState &oy); - bool ReadNextPage(OggSyncState &oy); - - DecoderCommand HandlePackets(); - DecoderCommand HandlePacket(const ogg_packet &packet); - DecoderCommand HandleBOS(const ogg_packet &packet); - DecoderCommand HandleTags(const ogg_packet &packet); - DecoderCommand HandleAudio(const ogg_packet &packet); - - bool Seek(OggSyncState &oy, double where); -}; - -MPDOpusDecoder::~MPDOpusDecoder() -{ - g_free(output_buffer); - - if (opus_decoder != nullptr) - opus_decoder_destroy(opus_decoder); - - if (os_initialized) - ogg_stream_clear(&os); -} - -inline bool -MPDOpusDecoder::ReadFirstPage(OggSyncState &oy) -{ - assert(!os_initialized); - - if (!oy.ExpectFirstPage(os)) - return false; - - os_initialized = true; - return true; -} - -inline bool -MPDOpusDecoder::ReadNextPage(OggSyncState &oy) -{ - assert(os_initialized); - - ogg_page page; - if (!oy.ExpectPage(page)) - return false; - - const auto page_serialno = ogg_page_serialno(&page); - if (page_serialno != os.serialno) - ogg_stream_reset_serialno(&os, page_serialno); - - ogg_stream_pagein(&os, &page); - return true; -} - -inline DecoderCommand -MPDOpusDecoder::HandlePackets() -{ - ogg_packet packet; - while (ogg_stream_packetout(&os, &packet) == 1) { - auto cmd = HandlePacket(packet); - if (cmd != DecoderCommand::NONE) - return cmd; - } - - return DecoderCommand::NONE; -} - -inline DecoderCommand -MPDOpusDecoder::HandlePacket(const ogg_packet &packet) -{ - if (packet.e_o_s) - return DecoderCommand::STOP; - - if (packet.b_o_s) - return HandleBOS(packet); - else if (!found_opus) - return DecoderCommand::STOP; - - if (IsOpusTags(packet)) - return HandleTags(packet); - - return HandleAudio(packet); -} - -/** - * Load the end-of-stream packet and restore the previous file - * position. - */ -static bool -LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno, - ogg_packet &packet) -{ - if (!is.CheapSeeking()) - /* we do this for local files only, because seeking - around remote files is expensive and not worth the - troubl */ - return -1; - - const auto old_offset = is.offset; - if (old_offset < 0) - return -1; - - /* create temporary Ogg objects for seeking and parsing the - EOS packet */ - OggSyncState oy(is, decoder); - ogg_stream_state os; - ogg_stream_init(&os, serialno); - - bool result = OggSeekFindEOS(oy, os, packet, is); - ogg_stream_clear(&os); - - /* restore the previous file position */ - is.Seek(old_offset, SEEK_SET, IgnoreError()); - - return result; -} - -/** - * Load the end-of-stream granulepos and restore the previous file - * position. - * - * @return -1 on error - */ -gcc_pure -static ogg_int64_t -LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno) -{ - ogg_packet packet; - if (!LoadEOSPacket(is, decoder, serialno, packet)) - return -1; - - return packet.granulepos; -} - -inline DecoderCommand -MPDOpusDecoder::HandleBOS(const ogg_packet &packet) -{ - assert(packet.b_o_s); - - if (found_opus || !IsOpusHead(packet)) - return DecoderCommand::STOP; - - unsigned channels; - if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || - !audio_valid_channel_count(channels)) - return DecoderCommand::STOP; - - assert(opus_decoder == nullptr); - assert(output_buffer == nullptr); - - opus_serialno = os.serialno; - found_opus = true; - - /* TODO: parse attributes from the OpusHead (sample rate, - channels, ...) */ - - int opus_error; - opus_decoder = opus_decoder_create(opus_sample_rate, channels, - &opus_error); - if (opus_decoder == nullptr) { - FormatError(opus_domain, "libopus error: %s", - opus_strerror(opus_error)); - return DecoderCommand::STOP; - } - - eos_granulepos = LoadEOSGranulePos(input_stream, &decoder, - opus_serialno); - const double duration = eos_granulepos >= 0 - ? double(eos_granulepos) / opus_sample_rate - : -1.0; - - const AudioFormat audio_format(opus_sample_rate, - SampleFormat::S16, channels); - decoder_initialized(decoder, audio_format, - eos_granulepos > 0, duration); - frame_size = audio_format.GetFrameSize(); - - /* allocate an output buffer for 16 bit PCM samples big enough - to hold a quarter second, larger than 120ms required by - libopus */ - output_size = audio_format.sample_rate / 4; - output_buffer = (opus_int16 *) - g_malloc(sizeof(*output_buffer) * output_size * - audio_format.channels); - - return decoder_get_command(decoder); -} - -inline DecoderCommand -MPDOpusDecoder::HandleTags(const ogg_packet &packet) -{ - ReplayGainInfo rgi; - rgi.Clear(); - - TagBuilder tag_builder; - - DecoderCommand cmd; - if (ScanOpusTags(packet.packet, packet.bytes, - &rgi, - &add_tag_handler, &tag_builder) && - !tag_builder.IsEmpty()) { - decoder_replay_gain(decoder, &rgi); - - Tag tag = tag_builder.Commit(); - cmd = decoder_tag(decoder, input_stream, std::move(tag)); - } else - cmd = decoder_get_command(decoder); - - return cmd; -} - -inline DecoderCommand -MPDOpusDecoder::HandleAudio(const ogg_packet &packet) -{ - assert(opus_decoder != nullptr); - - int nframes = opus_decode(opus_decoder, - (const unsigned char*)packet.packet, - packet.bytes, - output_buffer, output_size, - 0); - if (nframes < 0) { - LogError(opus_domain, opus_strerror(nframes)); - return DecoderCommand::STOP; - } - - if (nframes > 0) { - const size_t nbytes = nframes * frame_size; - auto cmd = decoder_data(decoder, input_stream, - output_buffer, nbytes, - 0); - if (cmd != DecoderCommand::NONE) - return cmd; - - if (packet.granulepos > 0) - decoder_timestamp(decoder, - double(packet.granulepos) - / opus_sample_rate); - } - - return DecoderCommand::NONE; -} - -bool -MPDOpusDecoder::Seek(OggSyncState &oy, double where_s) -{ - assert(eos_granulepos > 0); - assert(input_stream.seekable); - assert(input_stream.size > 0); - assert(input_stream.offset >= 0); - - const ogg_int64_t where_granulepos(where_s * opus_sample_rate); - - /* interpolate the file offset where we expect to find the - given granule position */ - /* TODO: implement binary search */ - InputStream::offset_type offset(where_granulepos * input_stream.size - / eos_granulepos); - - if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET)) - return false; - - decoder_timestamp(decoder, where_s); - return true; -} - -static void -mpd_opus_stream_decode(Decoder &decoder, - InputStream &input_stream) -{ - if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_OPUS) - return; - - /* rewind the stream, because ogg_codec_detect() has - moved it */ - input_stream.LockRewind(IgnoreError()); - - MPDOpusDecoder d(decoder, input_stream); - OggSyncState oy(input_stream, &decoder); - - if (!d.ReadFirstPage(oy)) - return; - - while (true) { - auto cmd = d.HandlePackets(); - if (cmd == DecoderCommand::SEEK) { - if (d.Seek(oy, decoder_seek_where(decoder))) - decoder_command_finished(decoder); - else - decoder_seek_error(decoder); - - continue; - } - - if (cmd != DecoderCommand::NONE) - break; - - if (!d.ReadNextPage(oy)) - break; - } -} - -static bool -mpd_opus_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - OggSyncState oy(is); - - ogg_stream_state os; - if (!oy.ExpectFirstPage(os)) - return false; - - /* read at most two more pages */ - unsigned remaining_pages = 2; - - bool result = false; - - ogg_packet packet; - while (true) { - int r = ogg_stream_packetout(&os, &packet); - if (r < 0) { - result = false; - break; - } - - if (r == 0) { - if (remaining_pages-- == 0) - break; - - if (!oy.ExpectPageIn(os)) { - result = false; - break; - } - - continue; - } - - if (packet.b_o_s) { - if (!IsOpusHead(packet)) - break; - - unsigned channels; - if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || - !audio_valid_channel_count(channels)) { - result = false; - break; - } - - result = true; - } else if (!result) - break; - else if (IsOpusTags(packet)) { - if (!ScanOpusTags(packet.packet, packet.bytes, - nullptr, - handler, handler_ctx)) - result = false; - - break; - } - } - - if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is)) - tag_handler_invoke_duration(handler, handler_ctx, - packet.granulepos / opus_sample_rate); - - ogg_stream_clear(&os); - - return result; -} - -static const char *const opus_suffixes[] = { - "opus", - "ogg", - "oga", - nullptr -}; - -static const char *const opus_mime_types[] = { - "audio/opus", - nullptr -}; - -const struct DecoderPlugin opus_decoder_plugin = { - "opus", - mpd_opus_init, - nullptr, - mpd_opus_stream_decode, - nullptr, - nullptr, - mpd_opus_scan_stream, - nullptr, - opus_suffixes, - opus_mime_types, -}; |