Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 1 | // Copyright 2018 The Cobalt Authors. All Rights Reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #include "starboard/shared/opus/opus_audio_decoder.h" |
| 16 | |
Chad Duffin | ac9ac06 | 2019-07-23 10:06:45 -0700 | [diff] [blame] | 17 | #include "starboard/common/log.h" |
| 18 | #include "starboard/common/string.h" |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 19 | #include "starboard/memory.h" |
| 20 | #include "starboard/shared/starboard/media/media_util.h" |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 21 | |
| 22 | namespace starboard { |
| 23 | namespace shared { |
| 24 | namespace opus { |
| 25 | |
| 26 | namespace { |
Andrew Top | cab7770 | 2019-09-17 13:14:36 -0700 | [diff] [blame] | 27 | |
| 28 | typedef struct { |
| 29 | int nb_streams; |
| 30 | int nb_coupled_streams; |
| 31 | unsigned char mapping[8]; |
| 32 | } VorbisLayout; |
| 33 | |
| 34 | /* Index is nb_channel-1 */ |
| 35 | static const VorbisLayout vorbis_mappings[8] = { |
| 36 | {1, 0, {0}}, /* 1: mono */ |
| 37 | {1, 1, {0, 1}}, /* 2: stereo */ |
| 38 | {2, 1, {0, 2, 1}}, /* 3: 1-d surround */ |
| 39 | {2, 2, {0, 1, 2, 3}}, /* 4: quadraphonic surround */ |
| 40 | {3, 2, {0, 4, 1, 2, 3}}, /* 5: 5-channel surround */ |
| 41 | {4, 2, {0, 4, 1, 2, 3, 5}}, /* 6: 5.1 surround */ |
| 42 | {4, 3, {0, 4, 1, 2, 3, 5, 6}}, /* 7: 6.1 surround */ |
| 43 | {5, 3, {0, 6, 1, 2, 3, 4, 5, 7}}, /* 8: 7.1 surround */ |
| 44 | }; |
| 45 | |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 46 | } // namespace |
| 47 | |
Chad Duffin | ac9ac06 | 2019-07-23 10:06:45 -0700 | [diff] [blame] | 48 | OpusAudioDecoder::OpusAudioDecoder( |
| 49 | const SbMediaAudioSampleInfo& audio_sample_info) |
| 50 | : audio_sample_info_(audio_sample_info) { |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 51 | int error; |
Andrew Top | cab7770 | 2019-09-17 13:14:36 -0700 | [diff] [blame] | 52 | int channels = audio_sample_info_.number_of_channels; |
| 53 | if (channels > 8 || channels < 1) { |
| 54 | SB_LOG(ERROR) << "Can't create decoder with " << channels << " channels"; |
| 55 | return; |
| 56 | } |
| 57 | |
| 58 | decoder_ = opus_multistream_decoder_create( |
| 59 | audio_sample_info_.samples_per_second, channels, |
| 60 | vorbis_mappings[channels - 1].nb_streams, |
| 61 | vorbis_mappings[channels - 1].nb_coupled_streams, |
| 62 | vorbis_mappings[channels - 1].mapping, &error); |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 63 | if (error != OPUS_OK) { |
| 64 | SB_LOG(ERROR) << "Failed to create decoder with error: " |
| 65 | << opus_strerror(error); |
| 66 | decoder_ = NULL; |
| 67 | return; |
| 68 | } |
| 69 | SB_DCHECK(decoder_ != NULL); |
| 70 | } |
| 71 | |
| 72 | OpusAudioDecoder::~OpusAudioDecoder() { |
| 73 | if (decoder_) { |
Andrew Top | cab7770 | 2019-09-17 13:14:36 -0700 | [diff] [blame] | 74 | opus_multistream_decoder_destroy(decoder_); |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 75 | } |
| 76 | } |
| 77 | |
| 78 | void OpusAudioDecoder::Initialize(const OutputCB& output_cb, |
| 79 | const ErrorCB& error_cb) { |
| 80 | SB_DCHECK(BelongsToCurrentThread()); |
| 81 | SB_DCHECK(output_cb); |
| 82 | SB_DCHECK(!output_cb_); |
| 83 | SB_DCHECK(error_cb); |
| 84 | SB_DCHECK(!error_cb_); |
| 85 | |
| 86 | output_cb_ = output_cb; |
| 87 | error_cb_ = error_cb; |
| 88 | } |
| 89 | |
| 90 | void OpusAudioDecoder::Decode(const scoped_refptr<InputBuffer>& input_buffer, |
| 91 | const ConsumedCB& consumed_cb) { |
| 92 | SB_DCHECK(BelongsToCurrentThread()); |
| 93 | SB_DCHECK(input_buffer); |
| 94 | SB_DCHECK(output_cb_); |
| 95 | |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 96 | if (stream_ended_) { |
| 97 | SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called."; |
| 98 | return; |
| 99 | } |
| 100 | |
Andrew Top | a7b1cfa | 2019-12-18 19:15:07 -0800 | [diff] [blame] | 101 | scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio( |
| 102 | audio_sample_info_.number_of_channels, GetSampleType(), |
| 103 | kSbMediaAudioFrameStorageTypeInterleaved, input_buffer->timestamp(), |
| 104 | audio_sample_info_.number_of_channels * frames_per_au_ * |
| 105 | starboard::media::GetBytesPerSample(GetSampleType())); |
| 106 | |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 107 | #if SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
Andrew Top | cab7770 | 2019-09-17 13:14:36 -0700 | [diff] [blame] | 108 | const char kDecodeFunctionName[] = "opus_multistream_decode"; |
| 109 | int decoded_frames = opus_multistream_decode( |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 110 | decoder_, static_cast<const unsigned char*>(input_buffer->data()), |
| 111 | input_buffer->size(), |
Andrew Top | a7b1cfa | 2019-12-18 19:15:07 -0800 | [diff] [blame] | 112 | reinterpret_cast<opus_int16*>(decoded_audio->buffer()), frames_per_au_, |
| 113 | 0); |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 114 | #else // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
Andrew Top | cab7770 | 2019-09-17 13:14:36 -0700 | [diff] [blame] | 115 | const char kDecodeFunctionName[] = "opus_multistream_decode_float"; |
| 116 | int decoded_frames = opus_multistream_decode_float( |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 117 | decoder_, static_cast<const unsigned char*>(input_buffer->data()), |
Andrew Top | a7b1cfa | 2019-12-18 19:15:07 -0800 | [diff] [blame] | 118 | input_buffer->size(), reinterpret_cast<float*>(decoded_audio->buffer()), |
| 119 | frames_per_au_, 0); |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 120 | #endif // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
Andrew Top | a7b1cfa | 2019-12-18 19:15:07 -0800 | [diff] [blame] | 121 | if (decoded_frames == OPUS_BUFFER_TOO_SMALL && |
| 122 | frames_per_au_ < kMaxOpusFramesPerAU) { |
| 123 | frames_per_au_ = kMaxOpusFramesPerAU; |
| 124 | // Send to decode again with the new |frames_per_au_|. |
| 125 | Decode(input_buffer, consumed_cb); |
| 126 | return; |
| 127 | } |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 128 | if (decoded_frames <= 0) { |
Andrew Top | a7b1cfa | 2019-12-18 19:15:07 -0800 | [diff] [blame] | 129 | // When the following check fails, it indicates that |frames_per_au_| is |
| 130 | // greater than or equal to |kMaxOpusFramesPerAU|, which should never happen |
| 131 | // for Opus. |
| 132 | SB_DCHECK(decoded_frames != OPUS_BUFFER_TOO_SMALL); |
| 133 | |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 134 | // TODO: Consider fill it with silence. |
| 135 | SB_LOG(ERROR) << kDecodeFunctionName |
| 136 | << "() failed with error code: " << decoded_frames; |
| 137 | error_cb_(kSbPlayerErrorDecode, |
| 138 | FormatString("%s() failed with error code: %d", |
| 139 | kDecodeFunctionName, decoded_frames)); |
| 140 | return; |
| 141 | } |
| 142 | |
Andrew Top | a7b1cfa | 2019-12-18 19:15:07 -0800 | [diff] [blame] | 143 | frames_per_au_ = decoded_frames; |
| 144 | decoded_audio->ShrinkTo(audio_sample_info_.number_of_channels * |
| 145 | frames_per_au_ * |
| 146 | starboard::media::GetBytesPerSample(GetSampleType())); |
| 147 | |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 148 | decoded_audios_.push(decoded_audio); |
Andrew Top | a7b1cfa | 2019-12-18 19:15:07 -0800 | [diff] [blame] | 149 | Schedule(consumed_cb); |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 150 | Schedule(output_cb_); |
| 151 | } |
| 152 | |
| 153 | void OpusAudioDecoder::WriteEndOfStream() { |
| 154 | SB_DCHECK(BelongsToCurrentThread()); |
| 155 | SB_DCHECK(output_cb_); |
| 156 | |
| 157 | // Opus has no dependent frames so we needn't flush the decoder. Set the |
| 158 | // flag to ensure that Decode() is not called when the stream is ended. |
| 159 | stream_ended_ = true; |
| 160 | // Put EOS into the queue. |
| 161 | decoded_audios_.push(new DecodedAudio); |
| 162 | |
| 163 | Schedule(output_cb_); |
| 164 | } |
| 165 | |
Andrew Top | 63c7ad4 | 2019-11-25 16:10:13 -0800 | [diff] [blame] | 166 | scoped_refptr<OpusAudioDecoder::DecodedAudio> OpusAudioDecoder::Read( |
| 167 | int* samples_per_second) { |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 168 | SB_DCHECK(BelongsToCurrentThread()); |
| 169 | SB_DCHECK(output_cb_); |
| 170 | SB_DCHECK(!decoded_audios_.empty()); |
| 171 | |
| 172 | scoped_refptr<DecodedAudio> result; |
| 173 | if (!decoded_audios_.empty()) { |
| 174 | result = decoded_audios_.front(); |
| 175 | decoded_audios_.pop(); |
| 176 | } |
Andrew Top | 63c7ad4 | 2019-11-25 16:10:13 -0800 | [diff] [blame] | 177 | *samples_per_second = audio_sample_info_.samples_per_second; |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 178 | return result; |
| 179 | } |
| 180 | |
| 181 | void OpusAudioDecoder::Reset() { |
| 182 | SB_DCHECK(BelongsToCurrentThread()); |
| 183 | |
| 184 | stream_ended_ = false; |
| 185 | while (!decoded_audios_.empty()) { |
| 186 | decoded_audios_.pop(); |
| 187 | } |
Andrew Top | 286dd78 | 2018-10-02 16:52:45 -0700 | [diff] [blame] | 188 | |
| 189 | CancelPendingJobs(); |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 190 | } |
| 191 | |
| 192 | bool OpusAudioDecoder::is_valid() const { |
| 193 | return decoder_ != NULL; |
| 194 | } |
| 195 | |
| 196 | SbMediaAudioSampleType OpusAudioDecoder::GetSampleType() const { |
| 197 | SB_DCHECK(BelongsToCurrentThread()); |
| 198 | #if SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
| 199 | return kSbMediaAudioSampleTypeInt16; |
| 200 | #else // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
| 201 | return kSbMediaAudioSampleTypeFloat32; |
| 202 | #endif // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
| 203 | } |
| 204 | |
Andrew Top | 6f0aefd | 2018-08-15 16:02:40 -0700 | [diff] [blame] | 205 | } // namespace opus |
| 206 | } // namespace shared |
| 207 | } // namespace starboard |