📄 audio_alsa_source.cpp
字号:
/* * The contents of this file are subject to the Mozilla Public * License Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is MPEG4IP. * * The Initial Developer of the Original Code is Cisco Systems Inc. * Portions created by Cisco Systems Inc. are * Copyright (C) Cisco Systems Inc. 2000-2002. All Rights Reserved. * * Contributor(s): * Dave Mackie dmackie@cisco.com * Bill May wmay@cisco.com * Sverker Abrahamsson sverker@abrahamsson.com */#include "mp4live.h"#include "audio_alsa_source.h"#include "audio_encoder.h"//#define DEBUG_TIMESTAMPS 1#ifdef HAVE_ALSACALSAAudioSource::CALSAAudioSource(CLiveConfig *pConfig) : CMediaSource() { SetConfig(pConfig); m_prevTimestamp = 0; m_timestampOverflowArray = NULL; m_timestampOverflowArrayIndex = 0; m_audioMaxBufferSize = 0;}int CALSAAudioSource::ThreadMain(void) { debug_message("alsa start"); while (true) { int rc; if (m_source) { rc = SDL_SemTryWait(m_myMsgQueueSemaphore); } else { rc = SDL_SemWait(m_myMsgQueueSemaphore); } // semaphore error if (rc == -1) { break; } // message pending if (rc == 0) { CMsg* pMsg = m_myMsgQueue.get_message(); if (pMsg != NULL) { switch (pMsg->get_value()) { case MSG_NODE_STOP_THREAD: DoStopCapture(); // ensure things get cleaned up delete pMsg; debug_message("alsa stop thread"); return 0; case MSG_NODE_START: DoStartCapture(); break; case MSG_NODE_STOP: DoStopCapture(); break; } delete pMsg; } } if (m_source) { try { //debug_message("processaudio"); ProcessAudio(); } catch (...) { error_message("alsa stop capture"); DoStopCapture(); break; } } } debug_message("alsa thread exit"); return -1;}void CALSAAudioSource::DoStartCapture(){ if (m_source) { return; } if (!Init()) { return; } m_source = true;}void CALSAAudioSource::DoStopCapture(){ if (!m_source) { return; } // CMediaSource::DoStopAudio(); snd_pcm_drop(m_pcmHandle); snd_pcm_close(m_pcmHandle); CHECK_AND_FREE(m_timestampOverflowArray); m_source = false;}bool CALSAAudioSource::Init(void){ bool rc = InitAudio(true); if (!rc) { return false; } if (!InitDevice()) { return false; } //#error we will have to remove this below - the sample rate will be rc = SetAudioSrc( PCMAUDIOFRAME, m_channelsConfigured, m_pConfig->GetIntegerValue(CONFIG_AUDIO_SAMPLE_RATE)); if (!rc) { return false; } // for live capture we can match the source to the destination // m_audioSrcSamplesPerFrame = m_audioDstSamplesPerFrame; // gets set m_pcmFrameSize = m_audioSrcSamplesPerFrame * m_audioSrcChannels * sizeof(u_int16_t); if (m_audioMaxBufferSize > 0) { size_t array_size; m_audioMaxBufferFrames = m_audioMaxBufferSize / m_pcmFrameSize; if (m_audioMaxBufferFrames == 0) { m_audioMaxBufferFrames = 1; } array_size = m_audioMaxBufferFrames * sizeof(*m_timestampOverflowArray); m_timestampOverflowArray = (Timestamp *)Malloc(array_size); memset(m_timestampOverflowArray, 0, array_size); } // maximum number of passes in ProcessAudio, approx 1 sec. m_maxPasses = m_audioSrcSampleRate / m_audioSrcSamplesPerFrame; return true;#if 0 init_failure: debug_message("audio initialization failed"); snd_pcm_close(m_pcmHandle); m_pcmHandle = -1; return false;#endif}bool CALSAAudioSource::InitDevice(void){ int err; // Capture stream snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE; // This structure contains information about // the hardware and can be used to specify the // configuration to be used for the PCM stream. snd_pcm_hw_params_t *hwparams; // Name of the PCM device, like plughw:0,0 const char *pcm_name = m_pConfig->GetStringValue(CONFIG_AUDIO_SOURCE_NAME); // Allocate the snd_pcm_hw_params_t structure on the stack. snd_pcm_hw_params_alloca(&hwparams); // Open PCM if ((err = snd_pcm_open(&m_pcmHandle, pcm_name, stream, 0)) < 0) { error_message("Failed to open %s: %s", pcm_name, snd_strerror(err)); return false; } // Init hwparams with full configuration space if ((err = snd_pcm_hw_params_any(m_pcmHandle, hwparams)) < 0) { error_message("Can not configure the PCM device %s: %s", pcm_name, snd_strerror(err)); return false; } // Enable resample#ifdef HAVE_SND_PCM_HW_PARAMS_SET_RATE_RESAMPLE if ((err = snd_pcm_hw_params_set_rate_resample(m_pcmHandle, hwparams, 1)) < 0) { error_message("Error enabling resample for PCM device %s: %s", pcm_name, snd_strerror(err)); return false; }#endif // Set access type to SND_PCM_ACCESS_RW_INTERLEAVED if ((err = snd_pcm_hw_params_set_access(m_pcmHandle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { error_message("Error setting access type for PCM device %s: %s", pcm_name, snd_strerror(err)); return false; } // Set sample format#ifdef WORDS_BIGENDIAN#define OUR_FORMAT SND_PCM_FORMAT_S16_BE#else#define OUR_FORMAT SND_PCM_FORMAT_S16_LE#endif if ((err = snd_pcm_hw_params_set_format(m_pcmHandle, hwparams, OUR_FORMAT)) < 0) { error_message("Couldn't set format for PCM device %s: %s", pcm_name, snd_strerror(err)); snd_pcm_close(m_pcmHandle); return false; } // Set number of channels m_channelsConfigured = m_pConfig->GetIntegerValue(CONFIG_AUDIO_CHANNELS); if ((err = snd_pcm_hw_params_set_channels(m_pcmHandle, hwparams, m_channelsConfigured)) < 0) { error_message("Couldn't set audio channels for PCM device %s: %s", pcm_name, snd_strerror(err)); snd_pcm_close(m_pcmHandle); return false; } // Set sample rate u_int32_t samplingRate = m_pConfig->GetIntegerValue(CONFIG_AUDIO_SAMPLE_RATE); u_int32_t targetSamplingRate = samplingRate; // TODO maybe use snd_pcm_hw_params_set_rate instead if ((err = snd_pcm_hw_params_set_rate_near(m_pcmHandle, hwparams, &targetSamplingRate, 0)) < 0) { error_message("Couldn't set sample rate for PCM device %s: %s", pcm_name, snd_strerror(err)); snd_pcm_close(m_pcmHandle); return false; } if (samplingRate != targetSamplingRate) { error_message("Couldn't set sample rate for PCM device %s", pcm_name); snd_pcm_close(m_pcmHandle); return false; } // Apply HW parameter settings to PCM device. This will also put it into PREPARED state if ((err = snd_pcm_hw_params(m_pcmHandle, hwparams)) < 0) { error_message("Couldn't set hw parameters PCM device %s: %s", pcm_name, snd_strerror(err)); snd_pcm_close(m_pcmHandle); return false; } debug_message("alsa init done"); return true;}void CALSAAudioSource::ProcessAudio(void){ int err; if (m_audioSrcFrameNumber == 0) { // Start the device if ((err = snd_pcm_start(m_pcmHandle)) < 0) { error_message("Couldn't start the PCM device: %s", snd_strerror(err)); } } snd_pcm_status_t *status; snd_pcm_status_alloca(&status); // for efficiency, process 1 second before returning to check for commands for (int pass = 0; pass < m_maxPasses && m_stop_thread == false; pass++) { u_int8_t* pcmFrameBuffer; pcmFrameBuffer = (u_int8_t*)malloc(m_pcmFrameSize); // The alsa frames is not the same as the pcm frames used to feed the encoder // Calculate how many alsa frames is neccesary to read to fill one pcm frame snd_pcm_uframes_t num_frames = m_pcmFrameSize / (m_audioSrcChannels * sizeof(u_int16_t)); // Check how many bytes there is to read in the buffer, it will be used to calculate timestamp snd_pcm_status(m_pcmHandle, status); unsigned long avail_bytes = snd_pcm_status_get_avail(status) * (m_audioSrcChannels * sizeof(u_int16_t)); Timestamp currentTime = GetTimestamp(); Timestamp timestamp; // Read num_frames frames from the PCM device // pointed to by pcm_handle to buffer capdata. // Returns the number of frames actually read. // TODO On certain alsa configurations, e.g. when using dsnoop with low sample rate, the period gets too small. What to do about that? snd_pcm_sframes_t framesRead; if((framesRead = snd_pcm_readi(m_pcmHandle, pcmFrameBuffer, num_frames)) == -EPIPE) { snd_pcm_prepare(m_pcmHandle); // Buffer Overrun. This means the audio buffer is full, and not capturing // we want to make the timestamp based on the previous one // When we hit this case, we start using the m_timestampOverflowArray // This will give us a timestamp for when the array is full. // // In other words, if we have a full audio buffer (ie: it's not loading // any more), we start storing the current timestamp into the array. // This will let us "catch up", and have a somewhat accurate timestamp // when we loop around // // wmay - I'm not convinced that this actually works - if the buffer // cleans up, we'll ignore m_timestampOverflowArray if (m_timestampOverflowArray != NULL && m_timestampOverflowArray[m_timestampOverflowArrayIndex] != 0) { timestamp = m_timestampOverflowArray[m_timestampOverflowArrayIndex]; } else { timestamp = m_prevTimestamp + SrcSamplesToTicks(avail_bytes); } if (m_timestampOverflowArray != NULL) m_timestampOverflowArray[m_timestampOverflowArrayIndex] = currentTime; debug_message("audio buffer full !"); } else { if (framesRead < (snd_pcm_sframes_t) num_frames) { error_message("Bad audio read. Expected %li frames, got %li", num_frames, framesRead); free(pcmFrameBuffer); continue; } // buffer is not full - so, we make the timestamp based on the number // of bytes in the buffer that we read. timestamp = currentTime - SrcSamplesToTicks(SrcBytesToSamples(avail_bytes)); if (m_timestampOverflowArray != NULL) m_timestampOverflowArray[m_timestampOverflowArrayIndex] = 0; } //debug_message("alsa read");#ifdef DEBUG_TIMESTAMPS debug_message("avail_bytes=%lu t="U64" timestamp="U64" delta="U64, avail_bytes, currentTime, timestamp, timestamp - m_prevTimestamp);#endif m_prevTimestamp = timestamp; if (m_timestampOverflowArray != NULL) { m_timestampOverflowArrayIndex = (m_timestampOverflowArrayIndex + 1) % m_audioMaxBufferFrames; }#ifdef DEBUG_TIMESTAMPS debug_message("pcm forward "U64" %u", timestamp, m_pcmFrameSize);#endif if (m_audioSrcFrameNumber == 0 && m_videoSource != NULL) { m_videoSource->RequestKeyFrame(timestamp); } m_audioSrcFrameNumber++; CMediaFrame *frame = new CMediaFrame(PCMAUDIOFRAME, pcmFrameBuffer, m_pcmFrameSize, timestamp); ForwardFrame(frame); }}bool CALSAAudioCapabilities::ProbeDevice(){ if (allSampleRateTableSize > NUM_ELEMENTS_IN_ARRAY(m_samplingRates)) { error_message("Number of sample rates exceeds audio cap array"); return false; } // Open PCM snd_pcm_t *pcm_handle; snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE; snd_pcm_hw_params_t *hwparams; snd_pcm_hw_params_alloca(&hwparams); int err; if ((err= snd_pcm_open(&pcm_handle, m_deviceName, stream, SND_PCM_NONBLOCK)) < 0) { error_message("Failed to open %s: %s", m_deviceName, snd_strerror(err)); return false; } m_canOpen = true; if ((err = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) { error_message("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err)); return false; } // union of valid sampling rates for MP3 and AAC // for all possible sampling rates u_int8_t i; for (i = 0; i < allSampleRateTableSize; i++) { u_int32_t targetRate = allSampleRateTable[i]; if (snd_pcm_hw_params_test_rate(pcm_handle, hwparams, targetRate, 0) != 0) { debug_message("audio device %s doesn't support sampling rate %u", m_deviceName, targetRate); continue; } debug_message("sampling rate %u supported", targetRate); // valid sampling rate m_samplingRates[m_numSamplingRates++] = targetRate; } // zero out remaining sampling rate entries for (i = m_numSamplingRates; i < allSampleRateTableSize; i++) { m_samplingRates[i] = 0; } snd_pcm_close(pcm_handle); return true;}void CALSAAudioCapabilities::Display (CLiveConfig *pConfig, char *msg, uint32_t max_len){ // might want to parse out CONFIG_AUDIO_INPUT_NAME a bit to make it // more English like... snprintf(msg, max_len, "%s, %s, %u Hz, %s", pConfig->GetStringValue(CONFIG_AUDIO_SOURCE_TYPE), pConfig->GetStringValue(CONFIG_AUDIO_SOURCE_NAME), pConfig->GetIntegerValue(CONFIG_AUDIO_SAMPLE_RATE), pConfig->GetIntegerValue(CONFIG_AUDIO_CHANNELS) == 1 ? "mono" : "stereo");}#endif
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -