bool ALSAPCMPlayer::Start(PCMDataSource &_source) { const unsigned new_sample_rate = _source.GetSampleRate(); if ((nullptr != source) && alsa_handle && (source->GetSampleRate() == new_sample_rate)) { /* just change the source / resume playback */ bool success = false; DispatchWait(io_service, [this, &_source, &success]() { bool recovered_from_underrun = false; switch (snd_pcm_state(alsa_handle.get())) { case SND_PCM_STATE_XRUN: if (0 != snd_pcm_prepare(alsa_handle.get())) return; else { recovered_from_underrun = true; success = true; } break; case SND_PCM_STATE_RUNNING: success = true; break; default: return; } if (success) { source = &_source; if (recovered_from_underrun) { const size_t n = buffer_size / channels; const size_t n_read = FillPCMBuffer(buffer.get(), n); if (!WriteFrames(n_read)) { success = false; return; } } if (success) StartEventHandling(); } }); if (success) return true; } Stop(); assert(!alsa_handle); AlsaHandleUniquePtr new_alsa_handle = MakeAlsaHandleUniquePtr(); { const char *alsa_device = ALSAEnv::GetALSADeviceName(); snd_pcm_t *raw_alsa_handle; int alsa_error = snd_pcm_open(&raw_alsa_handle, alsa_device, SND_PCM_STREAM_PLAYBACK, 0); if (alsa_error < 0) { LogFormat("snd_pcm_open(0x%p, \"%s\", SND_PCM_STREAM_PLAYBACK, 0) " "failed: %s", &alsa_handle, alsa_device, snd_strerror(alsa_error)); return false; } new_alsa_handle = MakeAlsaHandleUniquePtr(raw_alsa_handle); assert(new_alsa_handle); } unsigned latency = ALSAEnv::GetALSALatency(); channels = 1; bool big_endian_source = _source.IsBigEndian(); if (!SetParameters(*new_alsa_handle, new_sample_rate, big_endian_source, latency, channels)) return false; snd_pcm_sframes_t n_available = snd_pcm_avail(new_alsa_handle.get()); if (n_available <= 0) { LogFormat("snd_pcm_avail(0x%p) failed: %ld - %s", new_alsa_handle.get(), static_cast<long>(n_available), snd_strerror(static_cast<int>(n_available))); return false; } buffer_size = static_cast<snd_pcm_uframes_t>(n_available * channels); buffer = std::unique_ptr<int16_t[]>(new int16_t[buffer_size]); /* Why does Boost.Asio make it so hard to register a set of of standard poll() descriptors (struct pollfd)? */ int poll_fds_count = snd_pcm_poll_descriptors_count(new_alsa_handle.get()); if (poll_fds_count < 1) { LogFormat("snd_pcm_poll_descriptors_count(0x%p) returned %d", new_alsa_handle.get(), poll_fds_count); return false; } std::unique_ptr<struct pollfd[]> poll_fds( new struct pollfd[poll_fds_count]); BOOST_VERIFY( poll_fds_count == snd_pcm_poll_descriptors(new_alsa_handle.get(), poll_fds.get(), static_cast<unsigned>(poll_fds_count))); for (int i = 0; i < poll_fds_count; ++i) { if ((poll_fds[i].events & POLLIN) || (poll_fds[i].events & POLLPRI)) { read_poll_descs.emplace_back( boost::asio::posix::stream_descriptor(io_service, poll_fds[i].fd)); } if (poll_fds[i].events & POLLOUT) { write_poll_descs.emplace_back( boost::asio::posix::stream_descriptor(io_service, poll_fds[i].fd)); } } source = &_source; size_t n_read = FillPCMBuffer(buffer.get(), static_cast<size_t>(n_available)); if (0 == n_read) { LogFormat("ALSA PCMPlayer started with data source which " "does not deliver any data"); return false; } if (!WriteFrames(*new_alsa_handle, buffer.get(), static_cast<size_t>(n_available), false)) return false; alsa_handle = std::move(new_alsa_handle); StartEventHandling(); return true; }
size_t PCMMixerDataSource::GetData(int16_t *buffer, size_t n) { size_t copied_count = 0; PCMDataSource *sources_to_remove[MAX_MIXER_SOURCES_COUNT]; unsigned sources_to_remove_count = 0; const std::lock_guard<Mutex> protect(lock); for (unsigned i = 0; i < MAX_MIXER_SOURCES_COUNT; ++i) { PCMDataSource *source = sources[i]; if (nullptr != source) { bool do_byte_swap = ::IsBigEndian() != source->IsBigEndian(); if (0 == copied_count) { copied_count = source->GetData(buffer, n); if (0 == copied_count) { sources_to_remove[sources_to_remove_count++] = source; } else { if (do_byte_swap) ByteSwapAndLowerVolume(buffer, copied_count, vol_percent); else LowerVolume(buffer, copied_count, vol_percent); } } else { int16_t temp_buffer[1024]; size_t current_src_copied_count = 0; bool current_src_end = false; while (!current_src_end && (current_src_copied_count < copied_count)) { size_t bytes_to_read = std::min(copied_count - current_src_copied_count, ARRAY_SIZE(temp_buffer)); size_t iteration_read_count = source->GetData(temp_buffer, bytes_to_read); if (do_byte_swap) ByteSwapAndMixPCM(buffer + current_src_copied_count, temp_buffer, iteration_read_count, vol_percent); else MixPCM(buffer + current_src_copied_count, temp_buffer, iteration_read_count, vol_percent); current_src_copied_count += iteration_read_count; current_src_end = (iteration_read_count < bytes_to_read); } if (!current_src_end && (copied_count < n)) { size_t bytes_to_read = n - copied_count; size_t rest_read_count = source->GetData(buffer + copied_count, bytes_to_read); if (do_byte_swap) ByteSwapAndLowerVolume(buffer + copied_count, rest_read_count, vol_percent); else LowerVolume(buffer + copied_count, rest_read_count, vol_percent); copied_count += rest_read_count; current_src_end = (rest_read_count < bytes_to_read); } if (current_src_end) { sources_to_remove[sources_to_remove_count++] = source; } } } } if (sources_to_remove_count > 0) { for (unsigned i = 0; i < sources_to_remove_count; ++i) { for (unsigned j = 0; j < MAX_MIXER_SOURCES_COUNT; ++j) { if (sources_to_remove[i] == sources[j]) sources[j] = nullptr; } } } return copied_count; }