| // Copyright 2017 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "starboard/shared/ffmpeg/ffmpeg_audio_resampler.h" |
| |
| #include "starboard/log.h" |
| #include "starboard/memory.h" |
| #include "starboard/shared/starboard/media/media_util.h" |
| |
| namespace starboard { |
| namespace shared { |
| namespace ffmpeg { |
| |
| namespace { |
| |
| const int kMaxChannels = 8; |
| const int kMaxCachedSamples = 65536; |
| |
| int GetChannelLayoutFromChannels(int channels) { |
| if (channels == 1) { |
| return AV_CH_LAYOUT_MONO; |
| } |
| if (channels == 2) { |
| return AV_CH_LAYOUT_STEREO; |
| } |
| if (channels == 6) { |
| return AV_CH_LAYOUT_5POINT1; |
| } |
| if (channels == 8) { |
| return AV_CH_LAYOUT_7POINT1; |
| } |
| SB_NOTREACHED() << "Unsupported channel count: " << channels; |
| return AV_CH_LAYOUT_STEREO; |
| } |
| |
| int GetSampleFormatBySampleTypeAndStorageType( |
| SbMediaAudioSampleType sample_type, |
| SbMediaAudioFrameStorageType storage_type) { |
| if (sample_type == kSbMediaAudioSampleTypeInt16 && |
| storage_type == kSbMediaAudioFrameStorageTypeInterleaved) { |
| return AV_SAMPLE_FMT_S16; |
| } |
| if (sample_type == kSbMediaAudioSampleTypeFloat32 && |
| storage_type == kSbMediaAudioFrameStorageTypeInterleaved) { |
| return AV_SAMPLE_FMT_FLT; |
| } |
| if (sample_type == kSbMediaAudioSampleTypeInt16 && |
| storage_type == kSbMediaAudioFrameStorageTypePlanar) { |
| return AV_SAMPLE_FMT_S16P; |
| } |
| if (sample_type == kSbMediaAudioSampleTypeFloat32 && |
| storage_type == kSbMediaAudioFrameStorageTypePlanar) { |
| return AV_SAMPLE_FMT_FLTP; |
| } |
| SB_NOTREACHED() << "Unsupported sample type (" << sample_type << ") and " |
| << " storage type (" << storage_type << ") combination"; |
| return AV_SAMPLE_FMT_FLT; |
| } |
| |
| } // namespace |
| |
| AudioResampler::AudioResampler( |
| SbMediaAudioSampleType source_sample_type, |
| SbMediaAudioFrameStorageType source_storage_type, |
| int source_sample_rate, |
| SbMediaAudioSampleType destination_sample_type, |
| SbMediaAudioFrameStorageType destination_storage_type, |
| int destination_sample_rate, |
| int channels) |
| : source_sample_type_(source_sample_type), |
| source_storage_type_(source_storage_type), |
| destination_sample_type_(destination_sample_type), |
| destination_storage_type_(destination_storage_type), |
| destination_sample_rate_(destination_sample_rate), |
| channels_(channels), |
| start_pts_(-1), |
| samples_returned_(0), |
| eos_reached_(false) { |
| SB_DCHECK(channels_ <= kMaxChannels); |
| |
| context_ = avresample_alloc_context(); |
| SB_DCHECK(context_ != NULL); |
| |
| av_opt_set_int(context_, "in_channel_layout", |
| GetChannelLayoutFromChannels(channels_), 0); |
| av_opt_set_int(context_, "out_channel_layout", |
| GetChannelLayoutFromChannels(channels_), 0); |
| av_opt_set_int(context_, "in_sample_rate", source_sample_rate, 0); |
| av_opt_set_int(context_, "out_sample_rate", destination_sample_rate, 0); |
| av_opt_set_int(context_, "in_sample_fmt", |
| GetSampleFormatBySampleTypeAndStorageType(source_sample_type, |
| source_storage_type), |
| 0); |
| av_opt_set_int(context_, "out_sample_fmt", |
| GetSampleFormatBySampleTypeAndStorageType( |
| destination_sample_type, destination_storage_type), |
| 0); |
| av_opt_set_int(context_, "internal_sample_fmt", AV_SAMPLE_FMT_S16P, 0); |
| |
| int result = avresample_open(context_); |
| SB_DCHECK(!result); |
| } |
| |
| AudioResampler::~AudioResampler() { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| avresample_close(context_); |
| av_free(context_); |
| } |
| |
| scoped_refptr<AudioResampler::DecodedAudio> AudioResampler::Resample( |
| const scoped_refptr<DecodedAudio>& audio_data) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(audio_data); |
| SB_DCHECK(audio_data->sample_type() == source_sample_type_); |
| SB_DCHECK(audio_data->storage_type() == source_storage_type_); |
| SB_DCHECK(audio_data->channels() == channels_); |
| SB_DCHECK(!eos_reached_); |
| |
| if (channels_ > kMaxChannels) { |
| return new DecodedAudio; |
| } |
| |
| if (eos_reached_) { |
| return new DecodedAudio; |
| } |
| |
| if (start_pts_ < 0) { |
| start_pts_ = audio_data->pts(); |
| } |
| |
| uint8_t* input_buffers[kMaxChannels] = { |
| const_cast<uint8_t*>(audio_data->buffer())}; |
| if (source_storage_type_ == kSbMediaAudioFrameStorageTypePlanar) { |
| for (int i = 1; i < channels_; ++i) { |
| input_buffers[i] = const_cast<uint8_t*>( |
| audio_data->buffer() + audio_data->size() / channels_ * i); |
| } |
| } |
| |
| int result = avresample_convert(context_, NULL, 0, 0, input_buffers, 0, |
| audio_data->frames()); |
| SB_DCHECK(result == 0); |
| |
| return RetrieveOutput(); |
| } |
| |
| scoped_refptr<AudioResampler::DecodedAudio> AudioResampler::WriteEndOfStream() { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(!eos_reached_); |
| |
| eos_reached_ = true; |
| int result = avresample_convert(context_, NULL, 0, 0, NULL, 0, 0); |
| SB_DCHECK(result == 0); |
| |
| return RetrieveOutput(); |
| } |
| |
| scoped_refptr<AudioResampler::DecodedAudio> AudioResampler::RetrieveOutput() { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| int frames_in_buffer = avresample_available(context_); |
| SbMediaTime pts = start_pts_ + samples_returned_ * kSbMediaTimeSecond / |
| destination_sample_rate_; |
| int bytes_per_sample = |
| starboard::media::GetBytesPerSample(destination_sample_type_); |
| scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio( |
| channels_, destination_sample_type_, destination_storage_type_, pts, |
| frames_in_buffer * bytes_per_sample * channels_); |
| samples_returned_ += frames_in_buffer; |
| |
| if (frames_in_buffer > 0) { |
| uint8_t* output_buffers[kMaxChannels] = { |
| const_cast<uint8_t*>(decoded_audio->buffer())}; |
| if (destination_storage_type_ == kSbMediaAudioFrameStorageTypePlanar) { |
| for (int i = 1; i < channels_; ++i) { |
| output_buffers[i] = const_cast<uint8_t*>( |
| decoded_audio->buffer() + decoded_audio->size() / channels_ * i); |
| } |
| } |
| |
| int frames_read = |
| avresample_read(context_, output_buffers, frames_in_buffer); |
| SB_DCHECK(frames_read == frames_in_buffer); |
| } |
| |
| return decoded_audio; |
| } |
| |
| } // namespace ffmpeg |
| |
| namespace starboard { |
| namespace player { |
| namespace filter { |
| |
| // static |
| scoped_ptr<AudioResampler> AudioResampler::Create( |
| SbMediaAudioSampleType source_sample_type, |
| SbMediaAudioFrameStorageType source_storage_type, |
| int source_sample_rate, |
| SbMediaAudioSampleType destination_sample_type, |
| SbMediaAudioFrameStorageType destination_storage_type, |
| int destination_sample_rate, |
| int channels) { |
| return scoped_ptr<AudioResampler>(new ffmpeg::AudioResampler( |
| source_sample_type, source_storage_type, source_sample_rate, |
| destination_sample_type, destination_storage_type, |
| destination_sample_rate, channels)); |
| } |
| |
| } // namespace filter |
| } // namespace player |
| } // namespace starboard |
| |
| } // namespace shared |
| } // namespace starboard |