|
|
@ -4,322 +4,88 @@ |
|
|
|
#include <cstdint> |
|
|
|
#include <cstdint> |
|
|
|
#include <memory> |
|
|
|
#include <memory> |
|
|
|
#include <string_view> |
|
|
|
#include <string_view> |
|
|
|
|
|
|
|
#include "audio_decoder.hpp" |
|
|
|
#include "aac_decoder.h" |
|
|
|
#include "audio_task.hpp" |
|
|
|
#include "amr_decoder.h" |
|
|
|
#include "chunk.hpp" |
|
|
|
#include "audio_element.h" |
|
|
|
#include "fatfs_audio_input.hpp" |
|
|
|
#include "audio_event_iface.h" |
|
|
|
#include "freertos/portmacro.h" |
|
|
|
#include "audio_pipeline.h" |
|
|
|
#include "gpio_expander.hpp" |
|
|
|
#include "esp_err.h" |
|
|
|
#include "i2s_audio_output.hpp" |
|
|
|
#include "flac_decoder.h" |
|
|
|
#include "storage.hpp" |
|
|
|
#include "mp3_decoder.h" |
|
|
|
#include "stream_buffer.hpp" |
|
|
|
#include "ogg_decoder.h" |
|
|
|
#include "stream_info.hpp" |
|
|
|
#include "opus_decoder.h" |
|
|
|
#include "stream_message.hpp" |
|
|
|
#include "wav_decoder.h" |
|
|
|
|
|
|
|
|
|
|
|
namespace audio { |
|
|
|
#include "audio_output.hpp" |
|
|
|
|
|
|
|
|
|
|
|
// TODO: idk
|
|
|
|
static const char* kTag = "PLAYBACK"; |
|
|
|
static const std::size_t kMinElementBufferSize = 1024; |
|
|
|
static const char* kSource = "src"; |
|
|
|
|
|
|
|
static const char* kDecoder = "dec"; |
|
|
|
auto AudioPlayback::create(drivers::GpioExpander* expander, |
|
|
|
static const char* kSink = "sink"; |
|
|
|
std::shared_ptr<drivers::SdStorage> storage) |
|
|
|
|
|
|
|
|
|
|
|
static audio_element_status_t toStatus(void* status) { |
|
|
|
|
|
|
|
uintptr_t as_pointer_int = reinterpret_cast<uintptr_t>(status); |
|
|
|
|
|
|
|
return static_cast<audio_element_status_t>(as_pointer_int); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool endsWith(std::string_view str, std::string_view suffix) { |
|
|
|
|
|
|
|
return str.size() >= suffix.size() && |
|
|
|
|
|
|
|
0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void toLower(std::string& str) { |
|
|
|
|
|
|
|
std::transform(str.begin(), str.end(), str.begin(), |
|
|
|
|
|
|
|
[](unsigned char c) { return std::tolower(c); }); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace drivers { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto AudioPlayback::create(std::unique_ptr<IAudioOutput> output) |
|
|
|
|
|
|
|
-> cpp::result<std::unique_ptr<AudioPlayback>, Error> { |
|
|
|
-> cpp::result<std::unique_ptr<AudioPlayback>, Error> { |
|
|
|
audio_pipeline_handle_t pipeline; |
|
|
|
// Create everything
|
|
|
|
audio_element_handle_t fatfs_stream_reader; |
|
|
|
auto source = std::make_shared<FatfsAudioInput>(storage); |
|
|
|
audio_event_iface_handle_t event_interface; |
|
|
|
auto codec = std::make_shared<AudioDecoder>(); |
|
|
|
|
|
|
|
|
|
|
|
audio_pipeline_cfg_t pipeline_config = |
|
|
|
|
|
|
|
audio_pipeline_cfg_t(DEFAULT_AUDIO_PIPELINE_CONFIG()); |
|
|
|
|
|
|
|
pipeline = audio_pipeline_init(&pipeline_config); |
|
|
|
|
|
|
|
if (pipeline == NULL) { |
|
|
|
|
|
|
|
return cpp::fail(Error::PIPELINE_INIT); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fatfs_stream_cfg_t fatfs_stream_config = |
|
|
|
auto sink_res = I2SAudioOutput::create(expander); |
|
|
|
fatfs_stream_cfg_t(FATFS_STREAM_CFG_DEFAULT()); |
|
|
|
if (sink_res.has_error()) { |
|
|
|
fatfs_stream_config.type = AUDIO_STREAM_READER; |
|
|
|
return cpp::fail(ERR_INIT_ELEMENT); |
|
|
|
fatfs_stream_reader = fatfs_stream_init(&fatfs_stream_config); |
|
|
|
|
|
|
|
if (fatfs_stream_reader == NULL) { |
|
|
|
|
|
|
|
return cpp::fail(Error::FATFS_INIT); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
auto sink = sink_res.value(); |
|
|
|
|
|
|
|
|
|
|
|
audio_event_iface_cfg_t event_config = AUDIO_EVENT_IFACE_DEFAULT_CFG(); |
|
|
|
auto playback = std::make_unique<AudioPlayback>(); |
|
|
|
event_interface = audio_event_iface_init(&event_config); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio_pipeline_set_listener(pipeline, event_interface); |
|
|
|
// Configure the pipeline
|
|
|
|
audio_element_msg_set_listener(fatfs_stream_reader, event_interface); |
|
|
|
source->InputBuffer(&playback->stream_start_); |
|
|
|
audio_element_msg_set_listener(output->GetAudioElement(), event_interface); |
|
|
|
sink->OutputBuffer(&playback->stream_end_); |
|
|
|
|
|
|
|
playback->ConnectElements(source.get(), codec.get()); |
|
|
|
|
|
|
|
playback->ConnectElements(codec.get(), sink.get()); |
|
|
|
|
|
|
|
|
|
|
|
audio_pipeline_register(pipeline, fatfs_stream_reader, kSource); |
|
|
|
// Launch!
|
|
|
|
audio_pipeline_register(pipeline, output->GetAudioElement(), kSink); |
|
|
|
StartAudioTask("src", source); |
|
|
|
|
|
|
|
StartAudioTask("dec", codec); |
|
|
|
|
|
|
|
StartAudioTask("sink", sink); |
|
|
|
|
|
|
|
|
|
|
|
return std::make_unique<AudioPlayback>(output, pipeline, fatfs_stream_reader, |
|
|
|
return playback; |
|
|
|
event_interface); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
AudioPlayback::AudioPlayback(std::unique_ptr<IAudioOutput>& output, |
|
|
|
// TODO(jacqueline): think about sizes
|
|
|
|
audio_pipeline_handle_t pipeline, |
|
|
|
AudioPlayback::AudioPlayback() |
|
|
|
audio_element_handle_t source_element, |
|
|
|
: stream_start_(128, 128), stream_end_(128, 128) {} |
|
|
|
audio_event_iface_handle_t event_interface) |
|
|
|
|
|
|
|
: output_(std::move(output)), |
|
|
|
|
|
|
|
pipeline_(pipeline), |
|
|
|
|
|
|
|
source_element_(source_element), |
|
|
|
|
|
|
|
event_interface_(event_interface) {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AudioPlayback::~AudioPlayback() { |
|
|
|
AudioPlayback::~AudioPlayback() { |
|
|
|
audio_pipeline_remove_listener(pipeline_); |
|
|
|
// TODO(jacqueline): signal the end of all things, and maybe wait for it?
|
|
|
|
audio_element_msg_remove_listener(source_element_, event_interface_); |
|
|
|
|
|
|
|
audio_element_msg_remove_listener(output_->GetAudioElement(), |
|
|
|
|
|
|
|
event_interface_); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio_pipeline_stop(pipeline_); |
|
|
|
|
|
|
|
audio_pipeline_wait_for_stop(pipeline_); |
|
|
|
|
|
|
|
audio_pipeline_terminate(pipeline_); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ReconfigurePipeline(NONE); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio_pipeline_unregister(pipeline_, source_element_); |
|
|
|
|
|
|
|
audio_pipeline_unregister(pipeline_, output_->GetAudioElement()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio_event_iface_destroy(event_interface_); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio_pipeline_deinit(pipeline_); |
|
|
|
|
|
|
|
audio_element_deinit(source_element_); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void AudioPlayback::Play(const std::string& filename) { |
|
|
|
|
|
|
|
output_->SetSoftMute(true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (playback_state_ != STOPPED) { |
|
|
|
|
|
|
|
audio_pipeline_stop(pipeline_); |
|
|
|
|
|
|
|
audio_pipeline_wait_for_stop(pipeline_); |
|
|
|
|
|
|
|
audio_pipeline_terminate(pipeline_); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
playback_state_ = PLAYING; |
|
|
|
|
|
|
|
Decoder decoder = GetDecoderForFilename(filename); |
|
|
|
|
|
|
|
ReconfigurePipeline(decoder); |
|
|
|
|
|
|
|
audio_element_set_uri(source_element_, filename.c_str()); |
|
|
|
|
|
|
|
audio_pipeline_reset_ringbuffer(pipeline_); |
|
|
|
|
|
|
|
audio_pipeline_reset_elements(pipeline_); |
|
|
|
|
|
|
|
audio_pipeline_run(pipeline_); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
output_->SetSoftMute(false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void AudioPlayback::Toggle() { |
|
|
|
|
|
|
|
if (playback_state_ == PLAYING) { |
|
|
|
|
|
|
|
Pause(); |
|
|
|
|
|
|
|
} else if (playback_state_ == PAUSED) { |
|
|
|
|
|
|
|
Resume(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void AudioPlayback::Resume() { |
|
|
|
auto AudioPlayback::Play(const std::string& filename) -> void { |
|
|
|
if (playback_state_ == PAUSED) { |
|
|
|
StreamInfo info; |
|
|
|
ESP_LOGI(kTag, "resuming"); |
|
|
|
info.Path(filename); |
|
|
|
playback_state_ = PLAYING; |
|
|
|
|
|
|
|
audio_pipeline_resume(pipeline_); |
|
|
|
|
|
|
|
output_->SetSoftMute(false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
void AudioPlayback::Pause() { |
|
|
|
|
|
|
|
if (GetPlaybackState() == PLAYING) { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "pausing"); |
|
|
|
|
|
|
|
output_->SetSoftMute(true); |
|
|
|
|
|
|
|
playback_state_ = PAUSED; |
|
|
|
|
|
|
|
audio_pipeline_pause(pipeline_); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto AudioPlayback::GetPlaybackState() const -> PlaybackState { |
|
|
|
std::array<std::byte, 128> dest; |
|
|
|
return playback_state_; |
|
|
|
auto len = WriteMessage( |
|
|
|
} |
|
|
|
TYPE_STREAM_INFO, [&](auto enc) { return info.Encode(enc); }, dest); |
|
|
|
|
|
|
|
|
|
|
|
void AudioPlayback::ProcessEvents(uint16_t max_time_ms) { |
|
|
|
if (len.has_error()) { |
|
|
|
if (playback_state_ == STOPPED) { |
|
|
|
// TODO.
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
while (1) { |
|
|
|
// TODO: short delay, return error on fail
|
|
|
|
audio_event_iface_msg_t event; |
|
|
|
xMessageBufferSend(*stream_start_.Handle(), dest.data(), len.value(), |
|
|
|
esp_err_t err = audio_event_iface_listen(event_interface_, &event, |
|
|
|
portMAX_DELAY); |
|
|
|
pdMS_TO_TICKS(max_time_ms)); |
|
|
|
|
|
|
|
if (err != ESP_OK) { |
|
|
|
|
|
|
|
// Error should only be timeouts, so use a 'failure' as an indication that
|
|
|
|
|
|
|
|
// we're out of events to process.
|
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && |
|
|
|
|
|
|
|
event.source == (void*)decoder_ && |
|
|
|
|
|
|
|
event.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) { |
|
|
|
|
|
|
|
audio_element_info_t music_info; |
|
|
|
|
|
|
|
audio_element_getinfo(decoder_, &music_info); |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "sample_rate=%d, bits=%d, ch=%d", music_info.sample_rates, |
|
|
|
|
|
|
|
music_info.bits, music_info.channels); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && |
|
|
|
|
|
|
|
event.source == (void*)source_element_ && |
|
|
|
|
|
|
|
event.cmd == AEL_MSG_CMD_REPORT_STATUS) { |
|
|
|
|
|
|
|
audio_element_status_t status = toStatus(event.data); |
|
|
|
|
|
|
|
if (status == AEL_STATUS_STATE_FINISHED) { |
|
|
|
|
|
|
|
// TODO: Could we change the uri here? hmm.
|
|
|
|
|
|
|
|
ESP_LOGI(kTag, "finished reading input."); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && |
|
|
|
|
|
|
|
event.source == (void*)output_->GetAudioElement() && |
|
|
|
|
|
|
|
event.cmd == AEL_MSG_CMD_REPORT_STATUS) { |
|
|
|
|
|
|
|
audio_element_status_t status = toStatus(event.data); |
|
|
|
|
|
|
|
if (status == AEL_STATUS_STATE_FINISHED) { |
|
|
|
|
|
|
|
if (next_filename_ != "") { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "finished writing output. enqueing next."); |
|
|
|
|
|
|
|
Decoder decoder = GetDecoderForFilename(next_filename_); |
|
|
|
|
|
|
|
if (decoder == decoder_type_) { |
|
|
|
|
|
|
|
output_->SetSoftMute(true); |
|
|
|
|
|
|
|
audio_element_set_uri(source_element_, next_filename_.c_str()); |
|
|
|
|
|
|
|
audio_pipeline_reset_ringbuffer(pipeline_); |
|
|
|
|
|
|
|
audio_pipeline_reset_elements(pipeline_); |
|
|
|
|
|
|
|
audio_pipeline_change_state(pipeline_, AEL_STATE_INIT); |
|
|
|
|
|
|
|
audio_pipeline_run(pipeline_); |
|
|
|
|
|
|
|
output_->SetSoftMute(true); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
Play(next_filename_); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
next_filename_ = ""; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "finished writing output. stopping."); |
|
|
|
|
|
|
|
audio_pipeline_wait_for_stop(pipeline_); |
|
|
|
|
|
|
|
audio_pipeline_terminate(pipeline_); |
|
|
|
|
|
|
|
playback_state_ = STOPPED; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (event.need_free_data) { |
|
|
|
|
|
|
|
// AFAICT this never happens in practice, but it doesn't hurt to follow
|
|
|
|
|
|
|
|
// the api here anyway.
|
|
|
|
|
|
|
|
free(event.data); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void AudioPlayback::SetNextFile(const std::string& filename) { |
|
|
|
|
|
|
|
next_filename_ = filename; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void AudioPlayback::SetVolume(uint8_t volume) { |
|
|
|
|
|
|
|
output_->SetVolume(volume); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto AudioPlayback::GetVolume() const -> uint8_t { |
|
|
|
auto AudioPlayback::ConnectElements(IAudioElement* src, IAudioElement* sink) |
|
|
|
return output_->GetVolume(); |
|
|
|
-> void { |
|
|
|
} |
|
|
|
std::size_t chunk_size = |
|
|
|
|
|
|
|
std::max(src->InputMinChunkSize(), sink->InputMinChunkSize()); |
|
|
|
|
|
|
|
std::size_t buffer_size = std::max(kMinElementBufferSize, chunk_size * 2); |
|
|
|
|
|
|
|
|
|
|
|
auto AudioPlayback::GetDecoderForFilename(std::string filename) const |
|
|
|
auto buffer = std::make_unique<StreamBuffer>(chunk_size, buffer_size); |
|
|
|
-> Decoder { |
|
|
|
src->OutputBuffer(buffer.get()); |
|
|
|
toLower(filename); |
|
|
|
sink->OutputBuffer(buffer.get()); |
|
|
|
if (endsWith(filename, "mp3")) { |
|
|
|
element_buffers_.push_back(std::move(buffer)); |
|
|
|
return MP3; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (endsWith(filename, "amr") || endsWith(filename, "wamr")) { |
|
|
|
|
|
|
|
return AMR; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (endsWith(filename, "opus")) { |
|
|
|
|
|
|
|
return OPUS; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (endsWith(filename, "ogg")) { |
|
|
|
|
|
|
|
return OGG; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (endsWith(filename, "flac")) { |
|
|
|
|
|
|
|
return FLAC; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (endsWith(filename, "wav")) { |
|
|
|
|
|
|
|
return WAV; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (endsWith(filename, "aac") || endsWith(filename, "m4a") || |
|
|
|
|
|
|
|
endsWith(filename, "ts") || endsWith(filename, "mp4")) { |
|
|
|
|
|
|
|
return AAC; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return NONE; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto AudioPlayback::CreateDecoder(Decoder decoder) const |
|
|
|
|
|
|
|
-> audio_element_handle_t { |
|
|
|
|
|
|
|
if (decoder == MP3) { |
|
|
|
|
|
|
|
mp3_decoder_cfg_t config = DEFAULT_MP3_DECODER_CONFIG(); |
|
|
|
|
|
|
|
return mp3_decoder_init(&config); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (decoder == AMR) { |
|
|
|
|
|
|
|
amr_decoder_cfg_t config = DEFAULT_AMR_DECODER_CONFIG(); |
|
|
|
|
|
|
|
return amr_decoder_init(&config); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (decoder == OPUS) { |
|
|
|
|
|
|
|
opus_decoder_cfg_t config = DEFAULT_OPUS_DECODER_CONFIG(); |
|
|
|
|
|
|
|
return decoder_opus_init(&config); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (decoder == OGG) { |
|
|
|
|
|
|
|
ogg_decoder_cfg_t config = DEFAULT_OGG_DECODER_CONFIG(); |
|
|
|
|
|
|
|
return ogg_decoder_init(&config); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (decoder == FLAC) { |
|
|
|
|
|
|
|
flac_decoder_cfg_t config = DEFAULT_FLAC_DECODER_CONFIG(); |
|
|
|
|
|
|
|
return flac_decoder_init(&config); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (decoder == WAV) { |
|
|
|
|
|
|
|
wav_decoder_cfg_t config = DEFAULT_WAV_DECODER_CONFIG(); |
|
|
|
|
|
|
|
return wav_decoder_init(&config); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (decoder == AAC) { |
|
|
|
|
|
|
|
aac_decoder_cfg_t config = DEFAULT_AAC_DECODER_CONFIG(); |
|
|
|
|
|
|
|
return aac_decoder_init(&config); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return nullptr; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void AudioPlayback::ReconfigurePipeline(Decoder decoder) { |
|
|
|
|
|
|
|
if (decoder_type_ == decoder) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (decoder_type_ != NONE) { |
|
|
|
|
|
|
|
audio_pipeline_unlink(pipeline_); |
|
|
|
|
|
|
|
audio_element_msg_remove_listener(decoder_, event_interface_); |
|
|
|
|
|
|
|
audio_pipeline_unregister(pipeline_, decoder_); |
|
|
|
|
|
|
|
audio_element_deinit(decoder_); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (decoder != NONE) { |
|
|
|
|
|
|
|
decoder_ = CreateDecoder(decoder); |
|
|
|
|
|
|
|
decoder_type_ = decoder; |
|
|
|
|
|
|
|
audio_pipeline_register(pipeline_, decoder_, kDecoder); |
|
|
|
|
|
|
|
audio_element_msg_set_listener(decoder_, event_interface_); |
|
|
|
|
|
|
|
static const char* link_tag[3] = {kSource, kDecoder, kSink}; |
|
|
|
|
|
|
|
audio_pipeline_link(pipeline_, &link_tag[0], 3); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} // namespace drivers
|
|
|
|
} // namespace audio
|
|
|
|