blob: 811e1255f929705f8ed812571d7898f95667ef7e [file] [log] [blame]
// 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/win32/win32_audio_decoder.h"
#include <algorithm>
#include <queue>
#include "starboard/shared/starboard/thread_checker.h"
#include "starboard/shared/win32/atomic_queue.h"
#include "starboard/shared/win32/decrypting_decoder.h"
#include "starboard/shared/win32/error_utils.h"
#include "starboard/shared/win32/media_foundation_utils.h"
namespace starboard {
namespace shared {
namespace win32 {
namespace {
using Microsoft::WRL::ComPtr;
using ::starboard::shared::starboard::ThreadChecker;
std::vector<ComPtr<IMFMediaType>> Filter(
GUID subtype_guid, const std::vector<ComPtr<IMFMediaType>>& input) {
std::vector<ComPtr<IMFMediaType>> output;
for (size_t i = 0; i < input.size(); ++i) {
ComPtr<IMFMediaType> curr = input[i];
GUID guid_value;
HRESULT hr = curr->GetGUID(MF_MT_SUBTYPE, &guid_value);
CheckResult(hr);
if (subtype_guid == guid_value) {
output.push_back(curr);
}
}
return output;
}
class WinAudioFormat {
public:
explicit WinAudioFormat(const SbMediaAudioHeader& audio_header) {
WAVEFORMATEX* wave_format = WaveFormatTexPtr();
wave_format->nAvgBytesPerSec = audio_header.average_bytes_per_second;
wave_format->nBlockAlign = audio_header.block_alignment;
wave_format->nChannels = audio_header.number_of_channels;
wave_format->nSamplesPerSec = audio_header.samples_per_second;
wave_format->wBitsPerSample = audio_header.bits_per_sample;
wave_format->wFormatTag = audio_header.format_tag;
// TODO: Investigate this more.
wave_format->cbSize = kAudioExtraFormatBytes;
std::uint8_t* audio_specific_config = AudioSpecificConfigPtr();
// These are hard-coded audio specif audio configuration.
// Use |SbMediaAudioHeader::audio_specific_config| instead.
SB_DCHECK(kAudioExtraFormatBytes == 2);
// TODO: What do these values do?
audio_specific_config[0] = 0x12;
audio_specific_config[1] = 0x10;
}
WAVEFORMATEX* WaveFormatTexPtr() {
return reinterpret_cast<WAVEFORMATEX*>(full_structure);
}
uint8_t* AudioSpecificConfigPtr() {
return full_structure + sizeof(WAVEFORMATEX);
}
UINT32 Size() const {
return sizeof(full_structure);
}
private:
static const UINT32 kAudioExtraFormatBytes = 2;
uint8_t full_structure[sizeof(WAVEFORMATEX) + kAudioExtraFormatBytes];
};
class AbstractWin32AudioDecoderImpl : public AbstractWin32AudioDecoder {
public:
AbstractWin32AudioDecoderImpl(SbMediaAudioCodec codec,
SbMediaAudioFrameStorageType audio_frame_fmt,
SbMediaAudioSampleType sample_type,
const SbMediaAudioHeader& audio_header,
SbDrmSystem drm_system)
: thread_checker_(ThreadChecker::kSetThreadIdOnFirstCheck),
codec_(codec),
audio_frame_fmt_(audio_frame_fmt),
sample_type_(sample_type),
audio_header_(audio_header),
impl_("audio", CLSID_MSAACDecMFT, drm_system) {
Configure();
}
static GUID ConvertToWin32AudioCodec(SbMediaAudioCodec codec) {
switch (codec) {
case kSbMediaAudioCodecNone: { return MFAudioFormat_PCM; }
case kSbMediaAudioCodecAac: { return MFAudioFormat_AAC; }
case kSbMediaAudioCodecOpus: { return MFAudioFormat_Opus; }
case kSbMediaAudioCodecVorbis: {
SB_NOTIMPLEMENTED();
}
}
return MFAudioFormat_PCM;
}
void Consume(ComPtr<IMFSample> sample) {
DWORD buff_count = 0;
HRESULT hr = sample->GetBufferCount(&buff_count);
CheckResult(hr);
SB_DCHECK(buff_count == 1);
ComPtr<IMFMediaBuffer> media_buffer;
hr = sample->GetBufferByIndex(0, &media_buffer);
if (FAILED(hr)) {
return;
}
LONGLONG win32_timestamp = 0;
hr = sample->GetSampleTime(&win32_timestamp);
CheckResult(hr);
BYTE* buffer;
DWORD length;
hr = media_buffer->Lock(&buffer, NULL, &length);
CheckResult(hr);
SB_DCHECK(length);
const uint8_t* data = reinterpret_cast<uint8_t*>(buffer);
const size_t data_size = static_cast<size_t>(length);
DecodedAudioPtr data_ptr(new DecodedAudio(
audio_header_.number_of_channels, sample_type_, audio_frame_fmt_,
ConvertToMediaTime(win32_timestamp), data_size));
std::copy(data, data + data_size, data_ptr->buffer());
output_queue_.push(data_ptr);
media_buffer->Unlock();
}
void Configure() {
ComPtr<IMFMediaType> input_type;
HRESULT hr = MFCreateMediaType(&input_type);
CheckResult(hr);
WinAudioFormat audio_fmt(audio_header_);
hr = MFInitMediaTypeFromWaveFormatEx(
input_type.Get(),
audio_fmt.WaveFormatTexPtr(),
audio_fmt.Size());
CheckResult(hr);
hr = input_type->SetUINT32(MF_MT_AAC_PAYLOAD_TYPE, 0); // raw aac
CheckResult(hr);
GUID subtype = ConvertToWin32AudioCodec(codec_);
hr = input_type->SetGUID(MF_MT_SUBTYPE, subtype);
CheckResult(hr);
MediaTransform& decoder = impl_.GetDecoder();
std::vector<ComPtr<IMFMediaType>> available_types =
decoder.GetAvailableInputTypes();
available_types = Filter(subtype, available_types);
SB_DCHECK(available_types.size());
ComPtr<IMFMediaType> selected = available_types[0];
std::vector<GUID> attribs = {
MF_MT_AUDIO_BLOCK_ALIGNMENT,
MF_MT_AUDIO_SAMPLES_PER_SECOND,
MF_MT_AUDIO_AVG_BYTES_PER_SECOND,
MF_MT_AUDIO_NUM_CHANNELS,
};
for (auto it = attribs.begin(); it != attribs.end(); ++it) {
CopyUint32Property(*it, input_type.Get(), selected.Get());
}
decoder.SetInputType(selected);
decoder.SetOutputTypeBySubType(MFAudioFormat_Float);
}
bool TryWrite(const scoped_refptr<InputBuffer>& buff) SB_OVERRIDE {
SB_DCHECK(thread_checker_.CalledOnValidThread());
// The incoming audio is in ADTS format which has a 7 bytes header. But
// the audio decoder is configured to accept raw AAC. So we have to adjust
// the data, size, and subsample mapping to skip the ADTS header.
const int kADTSHeaderSize = 7;
const bool write_ok = impl_.TryWriteInputBuffer(buff, kADTSHeaderSize);
return write_ok;
}
void WriteEndOfStream() SB_OVERRIDE {
SB_DCHECK(thread_checker_.CalledOnValidThread());
impl_.Drain();
ComPtr<IMFSample> sample;
ComPtr<IMFMediaType> media_type;
while (impl_.ProcessAndRead(&sample, &media_type)) {
if (sample) {
Consume(sample);
}
}
output_queue_.push(new DecodedAudio);
}
scoped_refptr<DecodedAudio> ProcessAndRead() SB_OVERRIDE {
SB_DCHECK(thread_checker_.CalledOnValidThread());
ComPtr<IMFSample> sample;
ComPtr<IMFMediaType> media_type;
while (impl_.ProcessAndRead(&sample, &media_type)) {
if (sample) {
Consume(sample);
}
}
if (output_queue_.empty()) {
return NULL;
}
scoped_refptr<DecodedAudio> output = output_queue_.front();
output_queue_.pop();
return output;
}
void Reset() SB_OVERRIDE {
impl_.Reset();
std::queue<DecodedAudioPtr> empty;
output_queue_.swap(empty);
thread_checker_.Detach();
}
// The object is single-threaded and is driven by a dedicated thread.
// However the thread may gets destroyed and re-created over the life time of
// this object. We enforce that certain member functions can only called
// from one thread while still allows this object to be driven by different
// threads by:
// 1. The |thread_checker_| is initially created without attaching to any
// thread.
// 2. When a thread is destroyed, Reset() will be called which in turn calls
// Detach() on the |thread_checker_| to allow the object to attach to a
// new thread.
::starboard::shared::starboard::ThreadChecker thread_checker_;
const SbMediaAudioCodec codec_;
const SbMediaAudioHeader audio_header_;
const SbMediaAudioSampleType sample_type_;
const SbMediaAudioFrameStorageType audio_frame_fmt_;
DecryptingDecoder impl_;
std::queue<DecodedAudioPtr> output_queue_;
};
} // anonymous namespace.
scoped_ptr<AbstractWin32AudioDecoder> AbstractWin32AudioDecoder::Create(
SbMediaAudioCodec code,
SbMediaAudioFrameStorageType audio_frame_fmt,
SbMediaAudioSampleType sample_type,
const SbMediaAudioHeader& audio_header,
SbDrmSystem drm_system) {
return scoped_ptr<AbstractWin32AudioDecoder>(
new AbstractWin32AudioDecoderImpl(code, audio_frame_fmt, sample_type,
audio_header, drm_system));
}
} // namespace win32
} // namespace shared
} // namespace starboard