| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/fidl/cpp/clone.h> |
| #include <lib/fit/defer.h> |
| #include <lib/media/test/codec_buffer.h> |
| #include <lib/media/test/codec_client.h> |
| #include <lib/media/test/codec_output.h> |
| |
| #include <thread> |
| |
| #include "src/media/audio/lib/wav_writer/wav_writer.h" |
| #include "util.h" |
| |
| // Re. this example and threading: |
| // |
| // This example shows the handling needed to run a Codec using multiple client |
| // threads correctly. Any new codec client author should consider whether the |
| // benefits of using multiple threads are really worthwhile in the client's |
| // particular use case, or if goals can be met just fine with a single FIDL |
| // thread doing everything. Which is "best" depends on many factors, both |
| // technical and otherwise. The main downsides of doing everything on one FIDL |
| // thread from a codec point of view is potential lower responsiveness or timing |
| // glitches/hiccups especially if the single FIDL thread is used for additional |
| // non-FIDL things including _any_ time-consuming thing. If everything on the |
| // single FIDL thread is as async as possible and doesn't block the thread |
| // (including during things like buffer allocation via some other service), then |
| // it can work well - but fully achieving that of course brings its own fun. |
| // Consider that buffer allocation can _potentially_ take some duration that's |
| // not entirely under the client's control, so blocking a FIDL thread during |
| // that wouldn't be good for responsiveness of the FIDL thread. |
| // |
| // TODO(dustingreen): Write another simpler example that shows how to use the |
| // codec with only a single FIDL thread, single stream_lifetime_ordinal assumed, |
| // minimal output config change handling, maybe output config handling directly |
| // on the FIDL thread (despite potential for some duration not under the |
| // client's control there), not reserving any packets for the client, etc. |
| |
| // The VLOGF() and LOGF() macros are here because we want the calls sites to |
| // look like FX_VLOGF and FX_LOGF, but without hard-wiring to those. For now, |
| // printf() seems to work fine. |
| |
| namespace { |
| |
| // This example only has one stream_lifetime_ordinal which is 1. |
| // |
| // TODO(dustingreen): actually re-use the Codec instance for at least one more |
| // stream, even if it's just to decode the same data again. |
| constexpr uint64_t kStreamLifetimeOrdinal = 1; |
| |
| // Whether we actually output a wav depends on whether there are any args or |
| // not. |
| constexpr bool kWavWriterEnabled = true; |
| |
| std::unique_ptr<uint8_t[]> make_AudioSpecificConfig_from_ADTS_header( |
| std::unique_ptr<uint8_t[]>* input_bytes) { |
| std::unique_ptr<uint8_t[]> asc = std::make_unique<uint8_t[]>(2); |
| |
| // TODO(dustingreen): Remove this function as we don't need to be synthesizing |
| // oob_bytes from ADTS header data. |
| |
| // First, parse the stuff that's needed from the first ADTS header. |
| uint8_t* adts_header = static_cast<uint8_t*>(input_bytes->get()); |
| uint8_t profile_ObjectType; // name in AAC spec in adts_fixed_header |
| uint8_t sampling_frequency_index; // name in AAC spec in adts_fixed_header |
| uint8_t channel_configuration; // name in AAC spec in adts_fixed_header |
| profile_ObjectType = (adts_header[2] >> 6) & 0x3; |
| sampling_frequency_index = (adts_header[2] >> 2) & 0xf; |
| if (sampling_frequency_index >= 11) { |
| Exit("sampling frequency index too large: %d - exiting\n", |
| sampling_frequency_index); |
| } |
| channel_configuration = (adts_header[2] & 0x1) << 2 | (adts_header[3] >> 6); |
| |
| // Now let's convert these to the forms needed by AudioSpecificConfig. |
| uint8_t audioObjectType = |
| profile_ObjectType + 1; // see near Table 1.A.11, for AAC not MPEG-2 |
| uint8_t samplingFrequencyIndex = |
| sampling_frequency_index; // no conversion needed |
| uint8_t channelConfiguration = channel_configuration; // no conversion needed |
| uint8_t frameLengthFlag = 0; |
| uint8_t dependsOnCoreCoder = 0; |
| uint8_t extensionFlag = 0; |
| |
| // Now we are ready to build a two-byte AudioSpecificConfig. Not an |
| // AudioSpecificInfo as stated in avc_utils.cpp (AOSP) mind you, but an |
| // AudioSpecificConfig. |
| uint8_t* asc_header = static_cast<uint8_t*>(asc.get()); |
| asc_header[0] = (audioObjectType << 3) | (samplingFrequencyIndex >> 1); |
| asc_header[1] = (samplingFrequencyIndex << 7) | (channelConfiguration << 3) | |
| (frameLengthFlag << 2) | (dependsOnCoreCoder << 1) | |
| (extensionFlag << 0); |
| |
| return asc; |
| } |
| |
| } // namespace |
| |
| // On success, out_md contains the sha256 digest of the output audio data. This |
| // is intended as a golden-file value when this function is used as part of a |
| // test. This sha256 accounts for all the output WAV data and also the audio |
| // output format parameters. When the same input file is decoded we expect the |
| // sha256 to be the same. |
| // |
| // main_loop - the loop run by main(), codec_factory is bound to |
| // main_loop->dispatcher() |
| // codec_factory - codec_factory to take ownership of, use, and close by the |
| // time the function returns. This InterfacePtr would typically be obtained |
| // by calling |
| // startup_context->ConnectToEnvironmentService<fuchsia::mediacodec::CodecFactory>(). |
| // input_adts_file - This must be set and must be the filename of an input .adts |
| // file (input file extension not checked / doesn't matter). |
| // output_wav_file - If nullptr, don't write the audio data to a wav file. If |
| // non-nullptr, output audio data to the specified wav file. When used as |
| // an example, this will tend to be set. When used as a test, this will not |
| // be set. |
| // out_md - SHA256_DIGEST_LENGTH bytes long |
| void use_aac_decoder(async::Loop* main_loop, |
| fuchsia::mediacodec::CodecFactoryPtr codec_factory, |
| fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem, |
| const std::string& input_adts_file, |
| const std::string& output_wav_file, uint8_t* out_md) { |
| memset(out_md, 0, SHA256_DIGEST_LENGTH); |
| |
| // In this example code, we're using this async::Loop for everything |
| // FIDL-related in this function. We explicitly specify which loop for all |
| // activity initiated by this function. We post to the loop and want to make |
| // sure it's the same loop. We rely on it being the same loop for serializing |
| // sending of messages via CodecPtr. We need to serialize sending messages |
| // since ProxyController isn't thread safe for sending messages at least in |
| // the case where sent requests require responses. We could use something |
| // like a send lock, but that would require locking around every send, even |
| // those sends which are already on the loop thread, vs. what we're doing |
| // which only needs anything extra for sends we queue from threads that aren't |
| // the loop thread. |
| async::Loop loop(&kAsyncLoopConfigNoAttachToThread); |
| |
| // This example will give the loop it's own thread, so that the main thread |
| // can be used to sequence overall control of the Codec instance using a |
| // thread instead of chaining together a bunch of async activity (which would |
| // be more complicated to understand and serve little purpose in an example |
| // program like this). |
| loop.StartThread("use_aac_decoder_loop"); |
| // From this point forward, because the loop is already running, this example |
| // needs to be careful to be ready for all potential FIDL channel messages and |
| // errors before attaching the channel to the loop. The loop will continue |
| // running until after we've deleted all the stuff using the loop. |
| |
| // This example has these threads: |
| // * main thread - used for setup and to drive overall sequence progression |
| // without resorting to a bunch of chained async actions, so that the |
| // example can remain reasonably clear in terms of what overall big-picture |
| // steps are taken in what overall order. Note that any calls to FIDL |
| // interfaces are posted to the loop thread. |
| // * loop thread - this thread pumps all the FIDL interfaces in this example, |
| // using a separate thread from the main thread. This does require us to |
| // be a bit more careful to fully configure an interface to be ready to |
| // receive messages before binding a server endpoint locally, or fully |
| // ready to have error handler (or similar) called on the client end before |
| // sending the sever end somewhere else. |
| // * input thread - feeds in compressed input data - but note that the actual |
| // calls to any FIDL interface are posted to the loop thread. |
| // * output thread - accepts output data - but note that any actual calls to |
| // any FIDL interface are posted to the loop thread. |
| |
| // Current FIDL-related threading aspects: |
| // * Safe to call client-side proxy methods from multiple threads? If there |
| // is a response: No. If there is not a response: Maybe. While a |
| // no-response send might currently be safe for one-way sends without a |
| // response_handler, we shouldn't rely on that in this example (see TODO |
| // on call to CreateDecoder() below for only known current exception). |
| // Certainly any calls with a response need to be started from a single |
| // thread at a time. Posting to the loop thread for all client-side sends |
| // covers this bullet. |
| // * Safe to call a server-side response_handler's operator() from an |
| // arbitrary server-side thread? Yes, and seems likely to remain yes. |
| // * The FIDL events are set up within CodecClient's constructor before |
| // binding. |
| |
| VLOGF("reading adts file...\n"); |
| size_t input_size; |
| std::unique_ptr<uint8_t[]> input_bytes = |
| read_whole_file(input_adts_file.c_str(), &input_size); |
| VLOGF("done reading adts file.\n"); |
| |
| codec_factory.set_error_handler([](zx_status_t status) { |
| // TODO(dustingreen): get and print CodecFactory channel epitaph once that's |
| // possible. |
| LOGF("codec_factory failed - unexpected\n"); |
| }); |
| |
| VLOGF("before make_AudioSpecificConfig_from_ADTS_header()...\n"); |
| VLOGF("input_bytes->get(): %p\n", input_bytes.get()); |
| std::unique_ptr<uint8_t[]> asc = |
| make_AudioSpecificConfig_from_ADTS_header(&input_bytes); |
| VLOGF("after make_AudioSpecificConfig_from_ADTS_header()\n"); |
| std::vector<uint8_t> asc_vector(2); |
| for (int i = 0; i < 2; i++) { |
| asc_vector[i] = asc[i]; |
| } |
| |
| // Set all fields to 0 / default. |
| fuchsia::mediacodec::CreateDecoder_Params create_params = {}; |
| // TODO(dustingreen): Remove need for ADTS to specify any codec config since |
| // it's in-band, and maybe switch this program over to using .mp4 with |
| // AudioSpecificConfig() from the .mp4 file. |
| create_params.mutable_input_details()->set_format_details_version_ordinal(0); |
| create_params.mutable_input_details()->set_mime_type("audio/aac-adts"); |
| // We don't do this here for now, because we want to cover what happens when |
| // CreateDecoder() doesn't specify oob_bytes, but |
| // QueueInputFormatDetails() does. |
| // create_params.input_details.oob_bytes.reset(std::move(asc_vector)); |
| |
| fuchsia::media::FormatDetails full_input_details = |
| fidl::Clone(create_params.input_details()); |
| *full_input_details.mutable_oob_bytes() = std::move(asc_vector); |
| |
| // We're using CodecPtr here rather than CodecSyncPtr partly to have this |
| // example program be slightly more realistic (with respect to client programs |
| // that choose to use the async interface), and partly to avoid having to |
| // separately check the error return code of every call, since the sync proxy |
| // doesn't have any way to get an async error callback (that I've found). |
| // |
| // We let the CodecClient handle the creation of the CodecPtr, because the |
| // loop is already running, and we want the error handler to be set up by |
| // CodecClient in advance of the channel potentially being closed. |
| VLOGF("before CodecClient::CodecClient()...\n"); |
| CodecClient codec_client(&loop, std::move(sysmem)); |
| async::PostTask( |
| main_loop->dispatcher(), |
| [&codec_factory, create_params = std::move(create_params), |
| codec_client_request = codec_client.GetTheRequestOnce()]() mutable { |
| VLOGF("before codec_factory->CreateDecoder() (async)\n"); |
| codec_factory->CreateDecoder(std::move(create_params), |
| std::move(codec_client_request)); |
| }); |
| VLOGF("before codec_client.Start()...\n"); |
| // This does a Sync(), so after this we can drop the CodecFactory without it |
| // potentially cancelling our Codec create. |
| codec_client.Start(); |
| |
| // We don't need the CodecFactory any more, and at this point any Codec |
| // creation errors have had a chance to arrive via the |
| // codec_factory.set_error_handler() lambda. |
| // |
| // Unbind() is only safe to call on the interfaces's dispatcher thread. We |
| // also want to block the current thread until this is done, to avoid |
| // codec_factory potentially disappearing before this posted work finishes. |
| std::mutex unbind_mutex; |
| std::condition_variable unbind_done_condition; |
| bool unbind_done = false; |
| async::PostTask( |
| main_loop->dispatcher(), |
| [&codec_factory, &unbind_mutex, &unbind_done, &unbind_done_condition] { |
| codec_factory.Unbind(); |
| { // scope lock |
| std::lock_guard<std::mutex> lock(unbind_mutex); |
| unbind_done = true; |
| } // ~lock |
| unbind_done_condition.notify_all(); |
| // All of codec_factory, unbind_mutex, unbind_done, |
| // unbind_done_condition are potentially gone by this point. |
| }); |
| { // scope lock |
| std::unique_lock<std::mutex> lock(unbind_mutex); |
| while (!unbind_done) { |
| unbind_done_condition.wait(lock); |
| } |
| } // ~lock |
| FXL_DCHECK(unbind_done); |
| |
| // We use a separate thread to provide input data, separate thread for output |
| // data, and a separate FIDL thread (started above). This is to avoid the |
| // example being too simplistic to be of any use as a reference to authors of |
| // codec clients that use multiple threads, and to some degree, to keep the |
| // input handling and output handling code clear. |
| |
| // The captures here require the main thread to call in_thread->join() before |
| // the captures go out of scope. |
| VLOGF("before starting in_thread...\n"); |
| std::unique_ptr<std::thread> in_thread = std::make_unique<std::thread>( |
| [&codec_client, full_input_details = std::move(full_input_details), |
| &input_bytes, input_size]() mutable { |
| // First queue the full_input_details. |
| codec_client.QueueInputFormatDetails(kStreamLifetimeOrdinal, |
| std::move(full_input_details)); |
| |
| // "syncword" bits for ADTS are, starting at byte alignment: 0xFF 0xF. |
| // That's 12 1 bits, with the first 1 bit starting at a byte aligned |
| // boundary. |
| // |
| // Unfortunately, the "syncword" can show up in the middle of an aac |
| // frame, which means the syncword is more of a heuristic than a real |
| // sync. In this case the test file is clean, so by parsing the aac |
| // frame length we can skip forward and avoid getting fooled by the fake |
| // syncword(s). |
| auto queue_access_unit = [&codec_client](uint8_t* bytes, |
| size_t byte_count) { |
| size_t bytes_so_far = 0; |
| // printf("queuing offset: %ld byte_count: %zu\n", bytes - |
| // input_bytes.get(), byte_count); |
| while (bytes_so_far != byte_count) { |
| std::unique_ptr<fuchsia::media::Packet> packet = |
| codec_client.BlockingGetFreeInputPacket(); |
| |
| if (!packet->has_header()) { |
| Exit("broken server sent packet without header"); |
| } |
| |
| if (!packet->header().has_packet_index()) { |
| Exit("broken server sent packet without packet index"); |
| } |
| |
| const CodecBuffer& buffer = codec_client.GetInputBufferByIndex( |
| packet->header().packet_index()); |
| size_t bytes_to_copy = |
| std::min(byte_count - bytes_so_far, buffer.size_bytes()); |
| packet->set_stream_lifetime_ordinal(kStreamLifetimeOrdinal); |
| packet->set_start_offset(0); |
| packet->set_valid_length_bytes(bytes_to_copy); |
| packet->set_start_access_unit(true); |
| packet->set_known_end_access_unit(true); |
| memcpy(buffer.base(), bytes + bytes_so_far, bytes_to_copy); |
| codec_client.QueueInputPacket(std::move(packet)); |
| bytes_so_far += bytes_to_copy; |
| } |
| }; |
| int input_byte_count = input_size; |
| for (int i = 0; i < input_byte_count - 1;) { |
| if (!(input_bytes[i] == 0xFF && |
| ((input_bytes[i + 1] & 0xF0) == 0xF0))) { |
| printf("s"); |
| i++; |
| continue; |
| } |
| uint32_t bytes_left = input_byte_count - i; |
| uint8_t* adts_header = &input_bytes[i]; |
| bool protection_absent = (adts_header[1] & 1); |
| uint32_t adts_header_size = protection_absent ? 7 : 9; |
| if (bytes_left < adts_header_size) { |
| Exit( |
| "input data corrupt (maybe truncated) - vs header length - " |
| "bytes_left: %d adts_header_size: %d", |
| bytes_left, adts_header_size); |
| } |
| uint32_t aac_frame_length = ((adts_header[3] & 3) << 11) | |
| (adts_header[4] << 3) | |
| (adts_header[5] >> 5); |
| if (bytes_left < aac_frame_length) { |
| Exit( |
| "input data corrupt (maybe truncated) - vs frame length - " |
| "bytes_left: %d aac_frame_length: %d", |
| bytes_left, aac_frame_length); |
| } |
| queue_access_unit(&input_bytes[i], aac_frame_length); |
| i += aac_frame_length; |
| } |
| |
| // Send through QueueInputEndOfStream(). |
| codec_client.QueueInputEndOfStream(kStreamLifetimeOrdinal); |
| // input thread done |
| }); |
| |
| // Separate thread to process the output. |
| // |
| // codec_client outlives the thread. |
| std::unique_ptr<std::thread> out_thread = std::make_unique< |
| std::thread>([&codec_client, output_wav_file, out_md]() { |
| // The codec_client lock_ is not held for long durations in here, which is |
| // good since we're using this thread to do things like write to a WAV |
| // file. |
| media::audio::WavWriter<kWavWriterEnabled> wav_writer; |
| bool is_wav_initialized = false; |
| SHA256_CTX sha256_ctx; |
| SHA256_Init(&sha256_ctx); |
| // We allow the server to send multiple output constraints updates if it |
| // wants; see implementation of BlockingGetEmittedOutput() which will hide |
| // multiple constraints before the first packet from this code. In contrast |
| // we assert if the server sends multiple format updates without any |
| // packets in between, as that's not compliant with protocol rules. |
| // |
| // In this example, we only deal with one output format once we start seeing |
| // stream data show up, since WAV only supports a single format per file. |
| std::shared_ptr<const fuchsia::media::StreamOutputFormat> stream_format; |
| while (true) { |
| std::unique_ptr<CodecOutput> output = |
| codec_client.BlockingGetEmittedOutput(); |
| if (output->stream_lifetime_ordinal() != kStreamLifetimeOrdinal) { |
| Exit( |
| "server emitted a stream_lifetime_ordinal that client didn't set " |
| "on any input"); |
| } |
| if (output->end_of_stream()) { |
| VLOGF("output end_of_stream() - done with output\n"); |
| // Just "break;" would be more fragile under code modification. |
| goto end_of_output; |
| } |
| |
| const fuchsia::media::Packet& packet = output->packet(); |
| |
| if (!packet.has_header()) { |
| // The server should not generate any empty packets. |
| Exit("broken server sent packet without header"); |
| } |
| |
| // "packet" will live long enough because ~cleanup runs before ~output. |
| auto cleanup = fit::defer([&codec_client, &packet] { |
| // Using an auto call for this helps avoid losing track of the |
| // output_buffer. |
| codec_client.RecycleOutputPacket(fidl::Clone(packet.header())); |
| }); |
| |
| if (!packet.header().has_packet_index()) { |
| // The server should not generate any empty packets. |
| Exit("broken server sent packet without buffer index"); |
| } |
| |
| // We don't really care about output->constraints() here, for now. |
| |
| std::shared_ptr<const fuchsia::media::StreamOutputFormat> format = |
| output->format(); |
| // This will remain live long enough because this thread is the only |
| // thread that re-allocates output buffers. |
| const CodecBuffer& buffer = |
| codec_client.GetOutputBufferByIndex(packet.header().packet_index()); |
| |
| ZX_ASSERT(!stream_format || stream_format->has_format_details()); |
| if (stream_format && |
| (!format->has_format_details() || |
| !format->format_details().has_format_details_version_ordinal() || |
| format->format_details().format_details_version_ordinal() != |
| stream_format->format_details() |
| .format_details_version_ordinal())) { |
| Exit( |
| "codec server unexpectedly changed output format mid-stream - " |
| "unexpected for this stream"); |
| } |
| |
| if (!packet.has_valid_length_bytes() || |
| packet.valid_length_bytes() == 0) { |
| // The server should not generate any empty packets. |
| Exit("broken server sent empty packet"); |
| } |
| |
| if (!packet.has_start_offset()) { |
| // The server should not generate any empty packets. |
| Exit("broken server sent packet without start offset"); |
| } |
| |
| // We have a non-empty packet of the stream. |
| |
| if (!stream_format) { |
| // Every output has a config. This happens exactly once. |
| stream_format = format; |
| |
| if (!stream_format->has_format_details()) { |
| Exit("!format_details"); |
| } |
| |
| const fuchsia::media::FormatDetails& format_details = |
| stream_format->format_details(); |
| if (!format_details.has_domain()) { |
| Exit("!format.domain"); |
| } |
| |
| if (!format_details.domain().is_audio()) { |
| Exit("!format.domain.is_audio() - unexpected"); |
| } |
| const fuchsia::media::AudioFormat& audio = |
| format_details.domain().audio(); |
| if (!audio.is_uncompressed()) { |
| Exit("!audio.is_uncompressed() - unexpected"); |
| } |
| const fuchsia::media::AudioUncompressedFormat& uncompressed = |
| audio.uncompressed(); |
| if (!uncompressed.is_pcm()) { |
| Exit("!uncompressed.is_pcm() - unexpected"); |
| } |
| // For now, bail out if it's not audio PCM 16 bit 2 channel, if only |
| // because that's what we expect from the one test file so far, so if |
| // it's different it'll be good to know that immediately, for now. |
| // |
| // TODO(dustingreen): Try to figure out WAV channel ordering for > 2 |
| // channels so we can deal with > 2 channels correctly. Tolerate sample |
| // rates other than 44100. Tolerate bits per sample other than 16. Set |
| // up test input files for those cases. |
| // |
| // TODO(dustingreen): Once we have a video decoder, update this example |
| // to handle at least one video format, or create a separate example for |
| // video with some of the code in this file moved to a source_set. |
| const fuchsia::media::PcmFormat& pcm = uncompressed.pcm(); |
| if (pcm.channel_map.size() < 1 || pcm.channel_map.size() > 2) { |
| Exit( |
| "pcm.channel_map->size() outside range [1, 2] - unexpected - " |
| "actual: %zu\n", |
| pcm.channel_map.size()); |
| } |
| if (static_cast<fuchsia::media::AudioChannelId>(pcm.channel_map[0]) != |
| fuchsia::media::AudioChannelId::LF) { |
| Exit( |
| "pcm.channel_map[0] is unexpected given the input data used in " |
| "this example"); |
| } |
| if (pcm.channel_map.size() >= 2 && |
| static_cast<fuchsia::media::AudioChannelId>(pcm.channel_map[1]) != |
| fuchsia::media::AudioChannelId::RF) { |
| Exit( |
| "pcm.channel_map[1] is unexpected given the input data used in " |
| "this example"); |
| } |
| if (pcm.bits_per_sample != 16) { |
| Exit("pcm.bits_per_sample != 16 - unexpected - actual: %d", |
| pcm.bits_per_sample); |
| } |
| if (pcm.frames_per_second != 44100) { |
| Exit("pcm.frames_per_second != 44100 - unexpected - actual: %d", |
| pcm.frames_per_second); |
| } |
| if (!output_wav_file.empty()) { |
| if (!wav_writer.Initialize( |
| output_wav_file.c_str(), |
| fuchsia::media::AudioSampleFormat::SIGNED_16, |
| pcm.channel_map.size(), pcm.frames_per_second, |
| pcm.bits_per_sample)) { |
| Exit("wav_writer.Initialize() failed"); |
| } |
| is_wav_initialized = true; |
| } |
| SHA256_Update_AudioParameters(&sha256_ctx, pcm); |
| } |
| |
| // We have a non-empty buffer (EOS or not), so write the audio data to the |
| // WAV file. |
| if (is_wav_initialized) { |
| if (!wav_writer.Write(buffer.base() + packet.start_offset(), |
| packet.valid_length_bytes())) { |
| Exit("wav_writer.Write() failed"); |
| } |
| } |
| int16_t* int16_base = |
| reinterpret_cast<int16_t*>(buffer.base() + packet.start_offset()); |
| for (size_t iter = 0; |
| iter < packet.valid_length_bytes() / sizeof(int16_t); iter++) { |
| int16_t data_le = htole16(int16_base[iter]); |
| SHA256_Update(&sha256_ctx, &data_le, sizeof(data_le)); |
| } |
| } |
| end_of_output:; |
| if (is_wav_initialized) { |
| wav_writer.Close(); |
| } |
| if (!SHA256_Final(out_md, &sha256_ctx)) { |
| assert(false); |
| } |
| // output thread done |
| }); |
| |
| // decode some audio for a bit... in_thread, loop, out_thread, and the codec |
| // itself are taking care of it. |
| |
| // First wait for the input thread to be done feeding input data. Before the |
| // in_thread terminates, it'll have sent in a last empty EOS input buffer. |
| VLOGF("before in_thread->join()...\n"); |
| in_thread->join(); |
| VLOGF("after in_thread->join()\n"); |
| |
| // The EOS queued as an input buffer should cause the codec to output an EOS |
| // output buffer, at which point out_thread should terminate, after it has |
| // finalized the output WAV file. |
| VLOGF("before out_thread->join()...\n"); |
| out_thread->join(); |
| VLOGF("after out_thread->join()\n"); |
| |
| // Because CodecClient posted work to the loop which captured the CodecClient |
| // as "this", it's important that we ensure that all such work is done trying |
| // to run before we delete CodecClient. We need to know that the work posted |
| // using PostSerial() won't be trying to touch the channel or pointers that |
| // are owned by CodecClient before we close the channel or destruct |
| // CodecClient (which happens before ~loop). |
| // |
| // We call loop.Quit();loop.JoinThreads(); before codec_client.Stop() because |
| // there can be at least a RecycleOutputPacket() still working its way toward |
| // the Codec (via the loop) at this point, so doing |
| // loop.Quit();loop.JoinThreads(); first avoids potential FIDL message send |
| // errors. We're done decoding so we don't care whether any remaining queued |
| // messages toward the codec actually reach the codec. |
| // |
| // We use loop.Quit();loop.JoinThreads(); instead of loop.Shutdown() because |
| // we don't want the Shutdown() side-effect of failing the channel bindings. |
| // The Shutdown() will happen later. |
| // |
| // By ensuring that the loop is done running code before closing the channel |
| // (or loop.Shutdown()), we can close the channel cleanly and avoid mitigation |
| // of expected normal channel closure (or loop.Shutdown()) in any code that |
| // runs on the loop. This way, unexpected channel failure is the only case to |
| // worry about. |
| VLOGF("before loop.Quit()\n"); |
| loop.Quit(); |
| VLOGF("before loop.JoinThreads()...\n"); |
| loop.JoinThreads(); |
| VLOGF("after loop.JoinThreads()\n"); |
| |
| // Close the channel explicitly (just so we can more easily print messages |
| // before and after vs. ~codec_client). |
| VLOGF("before codec_client stop...\n"); |
| codec_client.Stop(); |
| VLOGF("after codec_client stop.\n"); |
| |
| // loop.Shutdown() the rest of the way explicitly (just so we can more easily |
| // print messages before and after vs. ~loop). If we did this before |
| // codec_client.Stop() it would cause the channel bindings to fail because |
| // async waits are failed as cancelled during Shutdown(). |
| VLOGF("before loop.Shutdown()...\n"); |
| loop.Shutdown(); |
| VLOGF("after loop.Shutdown()\n"); |
| |
| // The FIDL loop isn't running any more and the channels are closed. There |
| // are no other threads left that were started by this function. We can just |
| // delete stuff now. |
| |
| // success |
| // ~codec_client |
| // ~loop |
| // ~codec_factory |
| return; |
| } |