Skip to content

Commit

Permalink
Cpp camera stream client (#147)
Browse files Browse the repository at this point in the history
* Add missing h264 define.

* Add cpp-camera-stream-client.

* Remove useless cout.

* Add AlsaAudioSource config flags.

* Fix readme files.

* Use constants.

* Update VERSION
  • Loading branch information
mamaheux authored Jul 26, 2024
1 parent d36bf1a commit b90291b
Show file tree
Hide file tree
Showing 15 changed files with 812 additions and 28 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,6 @@ add_subdirectory(signaling-server)
if(OPENTERA_WEBRTC_ENABLE_EXAMPLES)
add_subdirectory(examples/cpp-data-channel-client)
add_subdirectory(examples/cpp-data-channel-client-reliability-tests)
add_subdirectory(examples/cpp-stream-client)
add_subdirectory(examples/cpp-video-stream-client)
add_subdirectory(examples/cpp-camera-stream-client)
endif()
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ Please read the [Code of Conduct](CODE_OF_CONDUCT.md) and [CONTRIBUTING](CONTRIB
### C++

* [data-channel-client](examples/cpp-data-channel-client)
* [stream-client](examples/cpp-stream-client)
* [cpp-video-stream-client](examples/cpp-video-stream-client)
* [cpp-camera-stream-client](examples/cpp-camera-stream-client)

### Python

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.2.4
1.2.5
4 changes: 2 additions & 2 deletions doc/cpp/mainpage.dox
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
\subsection data_channel_client_example Data Channel Client Example
\include{lineno} cpp-data-channel-client/main.cpp

\subsection stream_client_example Stream Client Example
\include{lineno} cpp-stream-client/main.cpp
\subsection video_stream_client_example Video Stream Client Example
\include{lineno} cpp-video-stream-client/main.cpp

*/
File renamed without changes.
292 changes: 292 additions & 0 deletions examples/cpp-camera-stream-client/AlsaPcmDevice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
#include "AlsaPcmDevice.h"

#include <iostream>
#include <map>

using namespace std;

PcmAudioFrame::PcmAudioFrame(PcmAudioFrameFormat format, size_t channelCount, size_t sampleCount)
: m_format(format),
m_channelCount(channelCount),
m_sampleCount(sampleCount),
m_hasOwnership(true)
{
m_data = new uint8_t[size()];
}

PcmAudioFrame::PcmAudioFrame(PcmAudioFrameFormat format, size_t channelCount, size_t sampleCount, uint8_t* data)
: m_format(format),
m_channelCount(channelCount),
m_sampleCount(sampleCount),
m_data(data),
m_hasOwnership(false)
{
}

PcmAudioFrame::PcmAudioFrame(const PcmAudioFrame& other)
: m_format(other.m_format),
m_channelCount(other.m_channelCount),
m_sampleCount(other.m_sampleCount),
m_hasOwnership(true)
{
m_data = new uint8_t[size()];
memcpy(m_data, other.m_data, size());
}

PcmAudioFrame::PcmAudioFrame(PcmAudioFrame&& other)
: m_format(other.m_format),
m_channelCount(other.m_channelCount),
m_sampleCount(other.m_sampleCount),
m_hasOwnership(other.m_hasOwnership)
{
m_data = other.m_data;

other.m_channelCount = 0;
other.m_sampleCount = 0;
other.m_data = nullptr;
}

PcmAudioFrame::~PcmAudioFrame()
{
if (m_data != nullptr && m_hasOwnership)
{
delete[] m_data;
}
}

PcmAudioFrame& PcmAudioFrame::operator=(const PcmAudioFrame& other)
{
if (m_format != other.m_format || m_channelCount != other.m_channelCount || m_sampleCount != other.m_sampleCount)
{
if (m_data != nullptr && m_hasOwnership)
{
delete[] m_data;
}

m_format = other.m_format;
m_channelCount = other.m_channelCount;
m_sampleCount = other.m_sampleCount;
m_hasOwnership = true;

m_data = new uint8_t[size()];
}
memcpy(m_data, other.m_data, size());

return *this;
}

PcmAudioFrame& PcmAudioFrame::operator=(PcmAudioFrame&& other)
{
if (m_data != nullptr && m_hasOwnership)
{
delete[] m_data;
}

m_format = other.m_format;
m_channelCount = other.m_channelCount;
m_sampleCount = other.m_sampleCount;
m_data = other.m_data;
m_hasOwnership = other.m_hasOwnership;

other.m_channelCount = 0;
other.m_sampleCount = 0;
other.m_data = nullptr;

return *this;
}

struct PcmParamsDeleter
{
void operator()(snd_pcm_hw_params_t* handle) { snd_pcm_hw_params_free(handle); }
};

AlsaPcmDevice::AlsaPcmDevice(
const std::string& device,
Stream stream,
PcmAudioFrameFormat format,
std::size_t channelCount,
std::size_t frameSampleCount,
std::size_t sampleFrequency)
: m_format(format),
m_channelCount(channelCount),
m_frameSampleCount(frameSampleCount)
{
int err;
snd_pcm_t* pcmHandlePointer;
snd_pcm_hw_params_t* paramsPointer;

if ((err = snd_pcm_open(&pcmHandlePointer, device.c_str(), convert(stream), 0)) < 0)
{
cerr << "Cannot open audio device: " << device << "(" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

unique_ptr<snd_pcm_t, PcmDeleter> pcmHandle(pcmHandlePointer);

if ((err = snd_pcm_hw_params_malloc(&paramsPointer)) < 0)
{
cerr << "Cannot allocate hardware parameter structure (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

unique_ptr<snd_pcm_hw_params_t, PcmParamsDeleter> params(paramsPointer);

if ((err = snd_pcm_hw_params_any(pcmHandle.get(), params.get())) < 0)
{
cerr << "Cannot initialize hardware parameter structure (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

if ((err = snd_pcm_hw_params_set_access(pcmHandle.get(), params.get(), SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
{
cerr << "Cannot set access type (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

if ((err = snd_pcm_hw_params_set_format(pcmHandle.get(), params.get(), convert(format))) < 0)
{
cerr << "Cannot set sample format (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

if ((err = snd_pcm_hw_params_set_rate(pcmHandle.get(), params.get(), static_cast<int>(sampleFrequency), 0)) < 0)
{
cerr << "Cannot set sample rate (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

if ((err = snd_pcm_hw_params_set_channels(pcmHandle.get(), params.get(), static_cast<int>(channelCount))) < 0)
{
cerr << "Cannot set channel count (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

snd_pcm_uframes_t periodSize = static_cast<snd_pcm_uframes_t>(frameSampleCount);
if ((err = snd_pcm_hw_params_set_period_size(pcmHandle.get(), params.get(), periodSize, 0)) < 0)
{
cerr << "Cannot set period size (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

if ((err = snd_pcm_hw_params(pcmHandle.get(), params.get())) < 0)
{
cerr << "Cannot set parameters (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

if ((err = snd_pcm_prepare(pcmHandle.get())) < 0)
{
cerr << "Cannot prepare audio interface for use (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}

m_pcmHandle = move(pcmHandle);
}

void AlsaPcmDevice::read(PcmAudioFrame& frame)
{
if (frame.format() != m_format || frame.channelCount() != m_channelCount ||
frame.sampleCount() != m_frameSampleCount)
{
cerr << "Invalid format, channelCount or sampleCount" << endl;
exit(EXIT_FAILURE);
}

snd_pcm_uframes_t periodSize = static_cast<snd_pcm_uframes_t>(m_frameSampleCount);
int err = snd_pcm_readi(m_pcmHandle.get(), frame.data(), periodSize);

if (err == -EPIPE)
{
// EPIPE means overrun
err = snd_pcm_recover(m_pcmHandle.get(), err, 1);
if (err >= 0)
{
err = snd_pcm_readi(m_pcmHandle.get(), frame.data(), periodSize);
}
}

if (err != periodSize)
{
cerr << "Read from audio interface failed (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}
}

void AlsaPcmDevice::write(const PcmAudioFrame& frame)
{
if (frame.format() != m_format || frame.channelCount() != m_channelCount ||
frame.sampleCount() != m_frameSampleCount)
{
cerr << "Invalid format, channelCount or sampleCount" << endl;
exit(EXIT_FAILURE);
}

snd_pcm_uframes_t periodSize = static_cast<snd_pcm_uframes_t>(m_frameSampleCount);
int err = snd_pcm_writei(m_pcmHandle.get(), frame.data(), periodSize);

if (err == -EPIPE)
{
// EPIPE means underrun
err = snd_pcm_recover(m_pcmHandle.get(), err, 1);
if (err >= 0)
{
err = snd_pcm_writei(m_pcmHandle.get(), frame.data(), periodSize);
}
}

if (err != periodSize)
{
cerr << "Write to audio interface failed (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}
}

void AlsaPcmDevice::wait()
{
int err = snd_pcm_wait(m_pcmHandle.get(), -1);
if (err != 1)
{
cerr << "snd_pcm_wait failed (" << err << ": " << snd_strerror(err) << ")" << endl;
exit(EXIT_FAILURE);
}
}

snd_pcm_stream_t AlsaPcmDevice::convert(Stream stream)
{
switch (stream)
{
case Stream::Playback:
return SND_PCM_STREAM_PLAYBACK;
case Stream::Capture:
return SND_PCM_STREAM_CAPTURE;
}

cerr << "Not supported stream" << endl;
exit(EXIT_FAILURE);
}

snd_pcm_format_t AlsaPcmDevice::convert(PcmAudioFrameFormat format)
{
static const map<PcmAudioFrameFormat, snd_pcm_format_t> Mapping(
{{PcmAudioFrameFormat::Signed8, SND_PCM_FORMAT_S8},
{PcmAudioFrameFormat::Signed16, SND_PCM_FORMAT_S16_LE},
{PcmAudioFrameFormat::Signed24, SND_PCM_FORMAT_S24_LE},
{PcmAudioFrameFormat::Signed32, SND_PCM_FORMAT_S32_LE},

{PcmAudioFrameFormat::Unsigned8, SND_PCM_FORMAT_U8},
{PcmAudioFrameFormat::Unsigned16, SND_PCM_FORMAT_U16_LE},
{PcmAudioFrameFormat::Unsigned24, SND_PCM_FORMAT_U24_LE},
{PcmAudioFrameFormat::Unsigned32, SND_PCM_FORMAT_U32_LE},

{PcmAudioFrameFormat::Float, SND_PCM_FORMAT_FLOAT_LE},
{PcmAudioFrameFormat::Double, SND_PCM_FORMAT_FLOAT64_LE}});

auto it = Mapping.find(format);
if (it != Mapping.end())
{
return it->second;
}

cerr << "Not supported format" << endl;
exit(EXIT_FAILURE);
}
Loading

0 comments on commit b90291b

Please sign in to comment.