| // 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_decoder_impl.h" |
| |
| #include <D3D11.h> |
| #include <mfapi.h> |
| #include <mferror.h> |
| #include <mfidl.h> |
| #include <wrl/client.h> |
| |
| #include <algorithm> |
| #include <numeric> |
| |
| #include "starboard/byte_swap.h" |
| #include "starboard/common/ref_counted.h" |
| #include "starboard/log.h" |
| #include "starboard/shared/win32/error_utils.h" |
| #include "starboard/shared/win32/media_common.h" |
| #include "starboard/shared/win32/media_foundation_utils.h" |
| |
| namespace starboard { |
| namespace shared { |
| namespace win32 { |
| |
| using ::starboard::shared::win32::CheckResult; |
| |
| namespace { |
| |
| // TODO: merge all kStreamId's. |
| const int kStreamId = 0; |
| |
| ComPtr<IMFSample> CreateSample(const void* data, |
| int size, |
| int64_t win32_timestamp) { |
| ComPtr<IMFMediaBuffer> buffer; |
| HRESULT hr = MFCreateMemoryBuffer(size, &buffer); |
| CheckResult(hr); |
| |
| BYTE* buffer_ptr; |
| hr = buffer->Lock(&buffer_ptr, 0, 0); |
| CheckResult(hr); |
| |
| SbMemoryCopy(buffer_ptr, data, size); |
| |
| hr = buffer->Unlock(); |
| CheckResult(hr); |
| |
| hr = buffer->SetCurrentLength(size); |
| CheckResult(hr); |
| |
| ComPtr<IMFSample> input; |
| hr = MFCreateSample(&input); |
| CheckResult(hr); |
| |
| hr = input->AddBuffer(buffer.Get()); |
| CheckResult(hr); |
| |
| // sample time is in 100 nanoseconds. |
| input->SetSampleTime(win32_timestamp); |
| return input; |
| } |
| |
| bool TryWriteToMediaProcessor(ComPtr<IMFTransform> media_processor, |
| ComPtr<IMFSample> input, |
| int stream_id) { |
| DWORD flag; |
| HRESULT hr = media_processor->GetInputStatus(stream_id, &flag); |
| CheckResult(hr); |
| |
| SB_DCHECK(MFT_INPUT_STATUS_ACCEPT_DATA == flag); |
| hr = media_processor->ProcessInput(kStreamId, input.Get(), 0); |
| |
| switch (hr) { |
| case S_OK: { // Data was sent to the media processor. |
| return true; |
| } |
| case MF_E_NOTACCEPTING: { |
| return false; |
| } |
| default: { |
| SB_NOTREACHED() << "Unexpected error."; |
| } |
| } |
| return false; |
| } |
| |
| bool StreamAllocatesMemory(DWORD out_stream_flags) { |
| static const DWORD kFlagAutoAllocateMemory = |
| MFT_OUTPUT_STREAM_PROVIDES_SAMPLES | |
| MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES; |
| |
| const bool output_stream_allocates_memory = |
| (out_stream_flags & kFlagAutoAllocateMemory) != 0; |
| return output_stream_allocates_memory; |
| } |
| |
| template <typename T> |
| void ReleaseIfNotNull(T** ptr) { |
| if (*ptr) { |
| (*ptr)->Release(); |
| *ptr = NULL; |
| } |
| } |
| |
| } // namespace |
| |
| DecoderImpl::DecoderImpl(const std::string& type, |
| MediaBufferConsumerInterface* media_buffer_consumer) |
| : type_(type), |
| media_buffer_consumer_(media_buffer_consumer), |
| discontinuity_(false) { |
| SB_DCHECK(media_buffer_consumer_); |
| } |
| |
| DecoderImpl::~DecoderImpl() { |
| } |
| |
| // static |
| ComPtr<IMFTransform> DecoderImpl::CreateDecoder(CLSID clsid) { |
| ComPtr<IMFTransform> decoder; |
| LPVOID* ptr_address = reinterpret_cast<LPVOID*>(decoder.GetAddressOf()); |
| HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, |
| IID_IMFTransform, ptr_address); |
| CheckResult(hr); |
| return decoder; |
| } |
| |
| void DecoderImpl::ActivateDecryptor(ComPtr<IMFMediaType> input_type) { |
| if (!decryptor_) { |
| return; |
| } |
| |
| HRESULT hr = decryptor_->SetInputType(kStreamId, input_type.Get(), |
| 0); // MFT_SET_TYPE_TEST_FLAGS. |
| CheckResult(hr); |
| |
| // Ensure that the decryptor and the decoder agrees on the protection of |
| // samples transferred between them. |
| ComPtr<IMFSampleProtection> decryption_sample_protection; |
| ComPtr<IMFSampleProtection> decoder_sample_protection; |
| DWORD decryption_protection_version; |
| DWORD decoder_protection_version; |
| DWORD protection_version; |
| BYTE* cert_data = NULL; |
| DWORD cert_data_size = 0; |
| BYTE* crypt_seed = NULL; |
| DWORD crypt_seed_size = 0; |
| ComPtr<IMFMediaType> decoder_input_type; |
| ComPtr<IMFMediaType> decoder_output_type; |
| |
| hr = decryptor_.As(&decryption_sample_protection); |
| CheckResult(hr); |
| hr = decryption_sample_protection->GetOutputProtectionVersion( |
| &decryption_protection_version); |
| CheckResult(hr); |
| hr = decoder_.As(&decoder_sample_protection); |
| CheckResult(hr); |
| hr = decoder_sample_protection->GetInputProtectionVersion( |
| &decoder_protection_version); |
| CheckResult(hr); |
| protection_version = |
| std::min(decoder_protection_version, decryption_protection_version); |
| if (protection_version < SAMPLE_PROTECTION_VERSION_RC4) { |
| SB_NOTREACHED(); |
| } |
| |
| hr = decoder_sample_protection->GetProtectionCertificate( |
| protection_version, |
| &cert_data, &cert_data_size); |
| CheckResult(hr); |
| |
| hr = decryption_sample_protection->InitOutputProtection( |
| protection_version, 0, cert_data, cert_data_size, |
| &crypt_seed, &crypt_seed_size); |
| CheckResult(hr); |
| |
| hr = decoder_sample_protection->InitInputProtection( |
| protection_version, 0, |
| crypt_seed, crypt_seed_size); |
| CheckResult(hr); |
| |
| CoTaskMemFree(cert_data); |
| CoTaskMemFree(crypt_seed); |
| |
| // Ensure that the input type of the decoder is the output type of the |
| // decryptor. |
| hr = decryptor_->GetOutputAvailableType( |
| kStreamId, |
| 0, // Type Index |
| decoder_input_type.ReleaseAndGetAddressOf()); |
| CheckResult(hr); |
| hr = decryptor_->SetOutputType(kStreamId, decoder_input_type.Get(), |
| 0); // _MFT_SET_TYPE_FLAGS |
| CheckResult(hr); |
| hr = decoder_->SetInputType(kStreamId, decoder_input_type.Get(), |
| 0); // _MFT_SET_TYPE_FLAGS |
| CheckResult(hr); |
| |
| // Start the decryptor, note that this should be better abstracted. |
| SendMFTMessage(decryptor_.Get(), MFT_MESSAGE_NOTIFY_BEGIN_STREAMING); |
| SendMFTMessage(decryptor_.Get(), MFT_MESSAGE_NOTIFY_START_OF_STREAM); |
| } |
| |
| bool DecoderImpl::TryWriteInputBuffer( |
| const void* data, |
| int size, |
| std::int64_t win32_timestamp, |
| const std::vector<std::uint8_t>& key_id, |
| const std::vector<std::uint8_t>& iv, |
| const std::vector<Subsample>& subsamples) { |
| // MFSampleExtension_CleanPoint is a key-frame for the video + audio. It is |
| // not set here because the win32 system is smart enough to figure this out. |
| // It will probably be totally ok to not set this at all. Resolution: If |
| // there are problems with win32 video decoding, come back to this and see |
| // if setting this will fix it. THis will be used if |
| // SbMediaVideoSampleInfo::is_key_frame is true inside of the this function |
| // (which will receive an InputBuffer). |
| // Set MFSampleExtension_CleanPoint attributes. |
| SB_DCHECK(decoder_); |
| ComPtr<IMFSample> input = CreateSample(data, size, win32_timestamp); |
| SB_DCHECK(decoder_); |
| |
| // Has to check both as sometimes the sample can contain an invalid key id. |
| // Better check the key id size is 16 and iv size is 8 or 16. |
| bool encrypted = !key_id.empty() && !iv.empty(); |
| if (encrypted) { |
| size_t iv_size = iv.size(); |
| const char kEightZeros[8] = {0}; |
| if (iv_size == 16 && SbMemoryCompare(iv.data() + 8, kEightZeros, 8) == 0) { |
| // For iv that is 16 bytes long but the the last 8 bytes is 0, we treat |
| // it as an 8 bytes iv. |
| iv_size = 8; |
| } |
| input->SetBlob(MFSampleExtension_Encryption_SampleID, |
| reinterpret_cast<const UINT8*>(iv.data()), |
| static_cast<UINT32>(iv_size)); |
| SB_DCHECK(key_id.size() == sizeof(GUID)); |
| GUID guid = *reinterpret_cast<const GUID*>(key_id.data()); |
| |
| guid.Data1 = SbByteSwapU32(guid.Data1); |
| guid.Data2 = SbByteSwapU16(guid.Data2); |
| guid.Data3 = SbByteSwapU16(guid.Data3); |
| |
| input->SetGUID(MFSampleExtension_Content_KeyID, guid); |
| |
| std::vector<DWORD> subsamples_data; |
| if (!subsamples.empty()) { |
| for (auto& subsample : subsamples) { |
| subsamples_data.push_back(subsample.clear_bytes); |
| subsamples_data.push_back(subsample.encrypted_bytes); |
| } |
| SB_DCHECK(std::accumulate(subsamples_data.begin(), subsamples_data.end(), |
| 0) == size); |
| } else { |
| subsamples_data.push_back(0); |
| subsamples_data.push_back(size); |
| } |
| input->SetBlob(MFSampleExtension_Encryption_SubSampleMappingSplit, |
| reinterpret_cast<UINT8*>(&subsamples_data[0]), |
| static_cast<UINT32>(subsamples_data.size() * sizeof(DWORD))); |
| } |
| |
| ComPtr<IMFTransform> media_processor = encrypted ? decryptor_ : decoder_; |
| const bool write_ok = |
| TryWriteToMediaProcessor(media_processor, input, kStreamId); |
| return write_ok; |
| } |
| |
| void DecoderImpl::DrainDecoder() { |
| // This is used during EOS to get the last few frames. |
| SB_DCHECK(decoder_); |
| SendMFTMessage(decoder_.Get(), MFT_MESSAGE_NOTIFY_END_OF_STREAM); |
| SendMFTMessage(decoder_.Get(), MFT_MESSAGE_COMMAND_DRAIN); |
| } |
| |
| bool DecoderImpl::DeliverOutputOnAllTransforms() { |
| SB_DCHECK(decoder_); |
| bool delivered = false; |
| if (decryptor_) { |
| while (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decryptor_)) { |
| HRESULT hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0); |
| if (hr == MF_E_NOTACCEPTING) { |
| // The protocol says that when ProcessInput() returns MF_E_NOTACCEPTING, |
| // there must be some output available. Retrieve the output and the next |
| // ProcessInput() should succeed. |
| while (ComPtr<IMFSample> sample_inner = |
| DeliverOutputOnTransform(decoder_)) { |
| DeliverDecodedSample(sample_inner); |
| delivered = true; |
| } |
| hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0); |
| } |
| CheckResult(hr); |
| delivered = true; |
| } |
| } |
| while (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decoder_)) { |
| DeliverDecodedSample(sample); |
| delivered = true; |
| } |
| return delivered; |
| } |
| |
| bool DecoderImpl::DeliverOneOutputOnAllTransforms() { |
| SB_DCHECK(decoder_); |
| bool delivered = false; |
| if (decryptor_) { |
| if (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decryptor_)) { |
| HRESULT hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0); |
| if (hr == MF_E_NOTACCEPTING) { |
| // The protocol says that when ProcessInput() returns MF_E_NOTACCEPTING, |
| // there must be some output available. Retrieve the output and the next |
| // ProcessInput() should succeed. |
| ComPtr<IMFSample> sample_inner = DeliverOutputOnTransform(decoder_); |
| if (sample_inner) { |
| DeliverDecodedSample(sample_inner); |
| delivered = true; |
| } |
| hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0); |
| CheckResult(hr); |
| return delivered; |
| } |
| CheckResult(hr); |
| delivered = true; |
| } |
| } |
| if (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decoder_)) { |
| DeliverDecodedSample(sample); |
| delivered = true; |
| } |
| return delivered; |
| } |
| |
| void DecoderImpl::set_decoder(Microsoft::WRL::ComPtr<IMFTransform> decoder) { |
| // Either new object is null or the old one is. |
| SB_DCHECK(!decoder || !decoder_); |
| decoder_ = decoder; |
| } |
| |
| void DecoderImpl::SendMFTMessage(IMFTransform* transform, |
| MFT_MESSAGE_TYPE msg) { |
| if (!transform) { |
| return; |
| } |
| ULONG_PTR data = 0; |
| HRESULT hr = transform->ProcessMessage(msg, data); |
| CheckResult(hr); |
| } |
| |
| void DecoderImpl::PrepareOutputDataBuffer( |
| ComPtr<IMFTransform> transform, |
| MFT_OUTPUT_DATA_BUFFER* output_data_buffer) { |
| output_data_buffer->dwStreamID = kStreamId; |
| output_data_buffer->pSample = NULL; |
| output_data_buffer->dwStatus = 0; |
| output_data_buffer->pEvents = NULL; |
| |
| MFT_OUTPUT_STREAM_INFO output_stream_info; |
| HRESULT hr = transform->GetOutputStreamInfo(kStreamId, &output_stream_info); |
| CheckResult(hr); |
| |
| // Each media sample (IMFSample interface) of output data from the MFT |
| // contains complete, unbroken units of data. The definition of a unit |
| // of data depends on the media type: For uncompressed video, a video |
| // frame; for compressed data, a compressed packet; for uncompressed audio, |
| // a single audio frame. |
| // |
| // For uncompressed audio formats, this flag is always implied. (It is valid |
| // to set the flag, but not required.) An uncompressed audio frame should |
| // never span more than one media sample. |
| SB_DCHECK((output_stream_info.dwFlags & MFT_OUTPUT_STREAM_WHOLE_SAMPLES) != |
| 0); |
| |
| if (StreamAllocatesMemory(output_stream_info.dwFlags)) { |
| // Try to let the IMFTransform allocate the memory if possible. |
| return; |
| } |
| |
| ComPtr<IMFSample> sample; |
| hr = MFCreateSample(&sample); |
| CheckResult(hr); |
| |
| ComPtr<IMFMediaBuffer> buffer; |
| hr = MFCreateAlignedMemoryBuffer(output_stream_info.cbSize, |
| output_stream_info.cbAlignment, &buffer); |
| |
| CheckResult(hr); |
| |
| hr = sample->AddBuffer(buffer.Get()); |
| CheckResult(hr); |
| |
| output_data_buffer->pSample = sample.Detach(); |
| } |
| |
| ComPtr<IMFSample> DecoderImpl::DeliverOutputOnTransform( |
| ComPtr<IMFTransform> transform) { |
| MFT_OUTPUT_DATA_BUFFER output_data_buffer; |
| PrepareOutputDataBuffer(transform, &output_data_buffer); |
| |
| const DWORD kFlags = 0; |
| const DWORD kNumberOfBuffers = 1; |
| DWORD status = 0; |
| HRESULT hr = transform->ProcessOutput(kFlags, kNumberOfBuffers, |
| &output_data_buffer, &status); |
| |
| SB_DCHECK(!output_data_buffer.pEvents); |
| |
| ComPtr<IMFSample> output = output_data_buffer.pSample; |
| ReleaseIfNotNull(&output_data_buffer.pEvents); |
| ReleaseIfNotNull(&output_data_buffer.pSample); |
| |
| if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { |
| ComPtr<IMFMediaType> media_type; |
| |
| hr = transform->GetOutputAvailableType(kStreamId, |
| 0, // TypeIndex |
| &media_type); |
| CheckResult(hr); |
| |
| hr = transform->SetOutputType(kStreamId, media_type.Get(), 0); |
| CheckResult(hr); |
| return NULL; |
| } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT || |
| output_data_buffer.dwStatus == MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) { |
| return NULL; |
| } |
| |
| SB_DCHECK(output); |
| if (discontinuity_) { |
| output->SetUINT32(MFSampleExtension_Discontinuity, TRUE); |
| discontinuity_ = false; |
| } |
| |
| CheckResult(hr); |
| return output; |
| } |
| |
| void DecoderImpl::DeliverDecodedSample(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 (SUCCEEDED(hr)) { |
| LONGLONG win32_timestamp = 0; |
| hr = sample->GetSampleTime(&win32_timestamp); |
| CheckResult(hr); |
| media_buffer_consumer_->Consume(media_buffer, win32_timestamp); |
| } |
| } |
| |
| } // namespace win32 |
| } // namespace shared |
| } // namespace starboard |