From b90291b143fb5f4a912164d98f38144017ade9f2 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Maheux <35638081+mamaheux@users.noreply.github.com> Date: Fri, 26 Jul 2024 12:13:58 -0400 Subject: [PATCH] Cpp camera stream client (#147) * Add missing h264 define. * Add cpp-camera-stream-client. * Remove useless cout. * Add AlsaAudioSource config flags. * Fix readme files. * Use constants. * Update VERSION --- CMakeLists.txt | 3 +- README.md | 3 +- VERSION | 2 +- doc/cpp/mainpage.dox | 4 +- .../.gitignore | 0 .../AlsaPcmDevice.cpp | 292 ++++++++++++++++++ .../cpp-camera-stream-client/AlsaPcmDevice.h | 162 ++++++++++ .../cpp-camera-stream-client/CMakeLists.txt | 36 +++ examples/cpp-camera-stream-client/README.md | 18 ++ examples/cpp-camera-stream-client/main.cpp | 268 ++++++++++++++++ examples/cpp-stream-client/README.md | 16 - examples/cpp-video-stream-client/.gitignore | 4 + .../CMakeLists.txt | 12 +- examples/cpp-video-stream-client/README.md | 18 ++ .../main.cpp | 2 +- 15 files changed, 812 insertions(+), 28 deletions(-) rename examples/{cpp-stream-client => cpp-camera-stream-client}/.gitignore (100%) create mode 100644 examples/cpp-camera-stream-client/AlsaPcmDevice.cpp create mode 100644 examples/cpp-camera-stream-client/AlsaPcmDevice.h create mode 100644 examples/cpp-camera-stream-client/CMakeLists.txt create mode 100644 examples/cpp-camera-stream-client/README.md create mode 100644 examples/cpp-camera-stream-client/main.cpp delete mode 100644 examples/cpp-stream-client/README.md create mode 100644 examples/cpp-video-stream-client/.gitignore rename examples/{cpp-stream-client => cpp-video-stream-client}/CMakeLists.txt (70%) create mode 100644 examples/cpp-video-stream-client/README.md rename examples/{cpp-stream-client => cpp-video-stream-client}/main.cpp (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa14e3dc..16f72831 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/README.md b/README.md index ee3ba3c0..e2871ea4 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/VERSION b/VERSION index e8ea05db..c813fe11 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.4 +1.2.5 diff --git a/doc/cpp/mainpage.dox b/doc/cpp/mainpage.dox index 7505b114..cc14ee01 100644 --- a/doc/cpp/mainpage.dox +++ b/doc/cpp/mainpage.dox @@ -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 */ diff --git a/examples/cpp-stream-client/.gitignore b/examples/cpp-camera-stream-client/.gitignore similarity index 100% rename from examples/cpp-stream-client/.gitignore rename to examples/cpp-camera-stream-client/.gitignore diff --git a/examples/cpp-camera-stream-client/AlsaPcmDevice.cpp b/examples/cpp-camera-stream-client/AlsaPcmDevice.cpp new file mode 100644 index 00000000..b99a37a2 --- /dev/null +++ b/examples/cpp-camera-stream-client/AlsaPcmDevice.cpp @@ -0,0 +1,292 @@ +#include "AlsaPcmDevice.h" + +#include +#include + +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 pcmHandle(pcmHandlePointer); + + if ((err = snd_pcm_hw_params_malloc(¶msPointer)) < 0) + { + cerr << "Cannot allocate hardware parameter structure (" << err << ": " << snd_strerror(err) << ")" << endl; + exit(EXIT_FAILURE); + } + + unique_ptr 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(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(channelCount))) < 0) + { + cerr << "Cannot set channel count (" << err << ": " << snd_strerror(err) << ")" << endl; + exit(EXIT_FAILURE); + } + + snd_pcm_uframes_t periodSize = static_cast(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(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(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 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); +} diff --git a/examples/cpp-camera-stream-client/AlsaPcmDevice.h b/examples/cpp-camera-stream-client/AlsaPcmDevice.h new file mode 100644 index 00000000..1dde1bc2 --- /dev/null +++ b/examples/cpp-camera-stream-client/AlsaPcmDevice.h @@ -0,0 +1,162 @@ +#ifndef ALSA_PCM_DEVICE_H +#define ALSA_PCM_DEVICE_H + +#include + +#include +#include +#include + +enum class PcmAudioFrameFormat : std::size_t +{ + Signed8 = 1, + Signed16 = 2, + Signed24 = 3, + SignedPadded24 = 4 + 64, + Signed32 = 4, + + Unsigned8 = 1 + 16, + Unsigned16 = 2 + 16, + Unsigned24 = 3 + 16, + UnsignedPadded24 = 4 + 16 + 64, + Unsigned32 = 4 + 16, + + Float = 4 + 32, + Double = 8 + 32 +}; + +inline std::size_t formatSize(PcmAudioFrameFormat format) +{ + return static_cast(format) & 0b1111; +} + +inline std::size_t pcmFrameSize(PcmAudioFrameFormat format, std::size_t channelCount, std::size_t sampleCount) +{ + return channelCount * sampleCount * formatSize(format); +} + +class PcmAudioFrame +{ + PcmAudioFrameFormat m_format; + std::size_t m_channelCount; + std::size_t m_sampleCount; + uint8_t* m_data; + bool m_hasOwnership; + +public: + PcmAudioFrame(PcmAudioFrameFormat format, std::size_t channelCount, std::size_t sampleCount); + PcmAudioFrame(PcmAudioFrameFormat format, std::size_t channelCount, std::size_t sampleCount, uint8_t* data); + + PcmAudioFrame(const PcmAudioFrame& other); + PcmAudioFrame(PcmAudioFrame&& other); + ~PcmAudioFrame(); + + PcmAudioFrameFormat format() const; + std::size_t channelCount() const; + std::size_t sampleCount() const; + + uint8_t* data(); + const uint8_t* data() const; + std::size_t size() const; + + bool hasOwnership() const; + + PcmAudioFrame& operator=(const PcmAudioFrame& other); + PcmAudioFrame& operator=(PcmAudioFrame&& other); + + uint8_t& operator[](std::size_t i); + uint8_t operator[](std::size_t i) const; + + void clear(); +}; + +inline PcmAudioFrameFormat PcmAudioFrame::format() const +{ + return m_format; +} + +inline std::size_t PcmAudioFrame::channelCount() const +{ + return m_channelCount; +} + +inline std::size_t PcmAudioFrame::sampleCount() const +{ + return m_sampleCount; +} + +inline uint8_t* PcmAudioFrame::data() +{ + return m_data; +} + +inline const uint8_t* PcmAudioFrame::data() const +{ + return m_data; +} + +inline std::size_t PcmAudioFrame::size() const +{ + return pcmFrameSize(m_format, m_channelCount, m_sampleCount); +} + +inline bool PcmAudioFrame::hasOwnership() const +{ + return m_hasOwnership; +} + +inline uint8_t& PcmAudioFrame::operator[](std::size_t i) +{ + return m_data[i]; +} + +inline uint8_t PcmAudioFrame::operator[](std::size_t i) const +{ + return m_data[i]; +} + +inline void PcmAudioFrame::clear() +{ + std::memset(m_data, 0, size()); +} + +class AlsaPcmDevice +{ +public: + enum class Stream + { + Playback = SND_PCM_STREAM_PLAYBACK, + Capture = SND_PCM_STREAM_CAPTURE + }; + +private: + struct PcmDeleter + { + void operator()(snd_pcm_t* handle) { snd_pcm_close(handle); } + }; + + PcmAudioFrameFormat m_format; + std::size_t m_channelCount; + std::size_t m_frameSampleCount; + + std::unique_ptr m_pcmHandle; + +public: + AlsaPcmDevice( + const std::string& device, + Stream stream, + PcmAudioFrameFormat format, + std::size_t channelCount, + std::size_t frameSampleCount, + std::size_t sampleFrequency); + + void read(PcmAudioFrame& frame); + void write(const PcmAudioFrame& frame); + void wait(); + +private: + static snd_pcm_stream_t convert(Stream stream); + static snd_pcm_format_t convert(PcmAudioFrameFormat format); +}; + +#endif diff --git a/examples/cpp-camera-stream-client/CMakeLists.txt b/examples/cpp-camera-stream-client/CMakeLists.txt new file mode 100644 index 00000000..50605a58 --- /dev/null +++ b/examples/cpp-camera-stream-client/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.14.0) + +if (UNIX AND NOT APPLE) + include_directories(${CMAKE_CURRENT_BINARY_DIR}) + + project(CppCameraStreamClient) + + set(LIBRARY_OUTPUT_PATH bin/${CMAKE_BUILD_TYPE}) + + include_directories(${OpenCV_INCLUDE_DIRS}) + include_directories(BEFORE SYSTEM ${webrtc_native_INCLUDE}) + include_directories(../../opentera-webrtc-native-client/3rdParty/json/include) + include_directories(../../opentera-webrtc-native-client/3rdParty/IXWebSocket) + include_directories(../../opentera-webrtc-native-client/3rdParty/cpp-httplib) + include_directories(../../opentera-webrtc-native-client/OpenteraWebrtcNativeClient/include) + + add_executable(CppCameraStreamClient main.cpp AlsaPcmDevice.cpp) + + target_link_libraries(CppCameraStreamClient + OpenteraWebrtcNativeClient + opencv_videoio + opencv_highgui + ) + + + target_link_libraries(CppCameraStreamClient + pthread + asound + ) + + if (NOT OPENTERA_WEBRTC_USE_SYSTEM_OPENCV) + add_dependencies(CppCameraStreamClient opencv_highgui opencv_videoio) + endif() + + set_property(TARGET CppCameraStreamClient PROPERTY CXX_STANDARD 17) +endif() diff --git a/examples/cpp-camera-stream-client/README.md b/examples/cpp-camera-stream-client/README.md new file mode 100644 index 00000000..115d67d2 --- /dev/null +++ b/examples/cpp-camera-stream-client/README.md @@ -0,0 +1,18 @@ +# cpp-camera-stream-client + +This example shows how to use the C++ library to create a client that sends and receives a video stream and an audio stream. +The video source is the webcam, the audio source is the default capture device and the audio output is the default playback device. +This example should be used with [web-stream-client](../web-stream-client). + +## How to use + +```bash +cd ../.. +mkdir build +cd build +cmake .. +cmake --build . --config Release|Debug + +cd bin/Release +./CppCameraStreamClient +``` diff --git a/examples/cpp-camera-stream-client/main.cpp b/examples/cpp-camera-stream-client/main.cpp new file mode 100644 index 00000000..83409805 --- /dev/null +++ b/examples/cpp-camera-stream-client/main.cpp @@ -0,0 +1,268 @@ +#include "AlsaPcmDevice.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace opentera; +using namespace std; + +class CvCameraCaptureVideoSource : public VideoSource +{ + atomic_bool m_stopped; + thread m_thread; + +public: + CvCameraCaptureVideoSource() + : VideoSource(VideoSourceConfiguration::create(false, true)), + m_stopped(false), + m_thread(&CvCameraCaptureVideoSource::run, this) + { + } + + ~CvCameraCaptureVideoSource() override + { + m_stopped.store(true); + m_thread.join(); + } + +private: + void run() + { + constexpr int CameraIndex = 0; + + cv::VideoCapture cap; + cap.open(CameraIndex); + if (!cap.isOpened()) + { + cerr << "No webcam found" << endl; + exit(EXIT_FAILURE); + } + + cv::Mat bgrImg; + while (!m_stopped.load()) + { + cap.read(bgrImg); + if (!bgrImg.empty()) + { + int64_t timestampUs = + chrono::duration_cast(chrono::steady_clock::now().time_since_epoch()).count(); + sendFrame(bgrImg, timestampUs); + } + } + } +}; + +constexpr uint32_t SoundCardTotalDelayMs = 0; +constexpr bool EchoCancellation = true; +constexpr bool AutoGainControl = true; +constexpr bool NoiseSuppression = true; +constexpr bool HighpassFilter = false; +constexpr bool StereoSwapping = false; +constexpr bool TransientSuppression = false; + +constexpr int BitsPerSample = 16; +constexpr PcmAudioFrameFormat AudioFormat = PcmAudioFrameFormat::Signed16; +constexpr int SampleRate = 48000; +constexpr size_t FrameSampleCount = SampleRate / 100; // 10 ms +constexpr size_t NumberOfChannels = 1; + +class AlsaAudioSource : public AudioSource +{ + atomic_bool m_stopped; + thread m_thread; + +public: + AlsaAudioSource() + : AudioSource( + AudioSourceConfiguration::create( + SoundCardTotalDelayMs, + EchoCancellation, + AutoGainControl, + NoiseSuppression, + HighpassFilter, + StereoSwapping, + TransientSuppression), + BitsPerSample, + SampleRate, + NumberOfChannels), + m_stopped(false), + m_thread(&AlsaAudioSource::run, this) + { + } + + ~AlsaAudioSource() override + { + m_stopped.store(true); + m_thread.join(); + } + +private: + void run() + { + AlsaPcmDevice captureDevice( + "default", + AlsaPcmDevice::Stream::Capture, + AudioFormat, + NumberOfChannels, + FrameSampleCount, + SampleRate); + + PcmAudioFrame frame(AudioFormat, NumberOfChannels, FrameSampleCount); + while (!m_stopped.load()) + { + captureDevice.read(frame); + sendFrame(frame.data(), frame.size() / bytesPerFrame()); + + captureDevice.wait(); + } + } +}; + +int main(int argc, char* argv[]) +{ + vector iceServers; + if (!IceServer::fetchFromServer("http://localhost:8080/iceservers", "abc", iceServers)) + { + cout << "IceServer::fetchFromServer failed" << endl; + iceServers.clear(); + } + + AlsaPcmDevice playbackDevice( + "default", + AlsaPcmDevice::Stream::Playback, + AudioFormat, + NumberOfChannels, + FrameSampleCount, + SampleRate); + + auto signalingServerConfiguration = + SignalingServerConfiguration::create("ws://localhost:8080/signaling", "C++", "chat", "abc"); + auto webrtcConfiguration = WebrtcConfiguration::create(iceServers); + auto videoStreamConfiguration = VideoStreamConfiguration::create(); + auto videoSource = make_shared(); + auto audioSource = make_shared(); + StreamClient + client(signalingServerConfiguration, webrtcConfiguration, videoStreamConfiguration, videoSource, audioSource); + + client.setOnSignalingConnectionOpened( + []() + { + // This callback is called from the internal client thread. + cout << "OnSignalingConnectionOpened" << endl; + }); + client.setOnSignalingConnectionClosed( + []() + { + // This callback is called from the internal client thread. + cout << "OnSignalingConnectionClosed" << endl; + }); + client.setOnSignalingConnectionError( + [](const string& error) + { + // This callback is called from the internal client thread. + cout << "OnSignalingConnectionError:" << endl << "\t" << error; + }); + + client.setOnRoomClientsChanged( + [](const vector& roomClients) + { + // This callback is called from the internal client thread. + cout << "OnRoomClientsChanged:" << endl; + for (const auto& c : roomClients) + { + cout << "\tid=" << c.id() << ", name=" << c.name() << ", isConnected=" << c.isConnected() << endl; + } + }); + + client.setOnClientConnected( + [](const Client& client) + { + // This callback is called from the internal client thread. + cout << "OnClientConnected:" << endl; + cout << "\tid=" << client.id() << ", name=" << client.name() << endl; + cv::namedWindow(client.id(), cv::WINDOW_AUTOSIZE); + }); + client.setOnClientDisconnected( + [](const Client& client) + { + // This callback is called from the internal client thread. + cout << "OnClientDisconnected:" << endl; + cout << "\tid=" << client.id() << ", name=" << client.name() << endl; + cv::destroyWindow(client.id()); + }); + client.setOnClientConnectionFailed( + [](const Client& client) + { + // This callback is called from the internal client thread. + cout << "OnClientConnectionFailed:" << endl; + cout << "\tid=" << client.id() << ", name=" << client.name() << endl; + }); + + client.setOnError( + [](const string& error) + { + // This callback is called from the internal client thread. + cout << "error:" << endl; + cout << "\t" << error << endl; + }); + + client.setLogger( + [](const string& message) + { + // This callback is called from the internal client thread. + cout << "log:" << endl; + cout << "\t" << message << endl; + }); + + client.setOnAddRemoteStream( + [](const Client& client) + { + // This callback is called from the internal client thread. + cout << "OnAddRemoteStream:" << endl; + cout << "\tid=" << client.id() << ", name=" << client.name() << endl; + }); + client.setOnRemoveRemoteStream( + [](const Client& client) + { + // This callback is called from the internal client thread. + cout << "OnRemoveRemoteStream:" << endl; + cout << "\tid=" << client.id() << ", name=" << client.name() << endl; + }); + client.setOnVideoFrameReceived( + [](const Client& client, const cv::Mat& bgrImg, uint64_t timestampUs) + { + // This callback is called from a WebRTC processing thread. + cv::imshow(client.id(), bgrImg); + cv::waitKey(1); + }); + client.setOnMixedAudioFrameReceived( + [&](const void* audioData, int bitsPerSample, int sampleRate, size_t numberOfChannels, size_t numberOfFrames) + { + if (bitsPerSample == BitsPerSample && numberOfChannels == NumberOfChannels && sampleRate == SampleRate) + { + playbackDevice.write(PcmAudioFrame(AudioFormat, NumberOfChannels, numberOfFrames, (uint8_t*)audioData)); + } + else + { + cout << "Receiving not supported audio frame (bitsPerSample=" << bitsPerSample + << ", numberOfChannels=" << numberOfChannels << ", sampleRate=" << sampleRate << ")" << endl; + } + }); + + client.connect(); + + cin.get(); + + return 0; +} diff --git a/examples/cpp-stream-client/README.md b/examples/cpp-stream-client/README.md deleted file mode 100644 index 024da65d..00000000 --- a/examples/cpp-stream-client/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# cpp-stream-client - -This example shows how to use the C++ library to create a client that sends and receives a video stream and an audio stream. This example should be used with [web-stream-client](../web-stream-client). - -## How to use - -```bash -cd ../.. -mkdir build -cd build -cmake .. -cmake --build . --config Release|Debug - -cd bin/Release -./CppStreamClient video_path -``` diff --git a/examples/cpp-video-stream-client/.gitignore b/examples/cpp-video-stream-client/.gitignore new file mode 100644 index 00000000..3967845b --- /dev/null +++ b/examples/cpp-video-stream-client/.gitignore @@ -0,0 +1,4 @@ +cmake-build-debug +cmake-build-release +build +.idea diff --git a/examples/cpp-stream-client/CMakeLists.txt b/examples/cpp-video-stream-client/CMakeLists.txt similarity index 70% rename from examples/cpp-stream-client/CMakeLists.txt rename to examples/cpp-video-stream-client/CMakeLists.txt index 5a1de138..228d0f3f 100644 --- a/examples/cpp-stream-client/CMakeLists.txt +++ b/examples/cpp-video-stream-client/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.14.0) include_directories(${CMAKE_CURRENT_BINARY_DIR}) -project(CppStreamClient) +project(CppVideoStreamClient) set(LIBRARY_OUTPUT_PATH bin/${CMAKE_BUILD_TYPE}) @@ -13,22 +13,22 @@ include_directories(../../opentera-webrtc-native-client/3rdParty/IXWebSocket) include_directories(../../opentera-webrtc-native-client/3rdParty/cpp-httplib) include_directories(../../opentera-webrtc-native-client/OpenteraWebrtcNativeClient/include) -add_executable(CppStreamClient main.cpp) +add_executable(CppVideoStreamClient main.cpp) -target_link_libraries(CppStreamClient +target_link_libraries(CppVideoStreamClient OpenteraWebrtcNativeClient opencv_videoio opencv_highgui ) if (NOT WIN32) - target_link_libraries(CppStreamClient + target_link_libraries(CppVideoStreamClient pthread ) endif() if (NOT OPENTERA_WEBRTC_USE_SYSTEM_OPENCV) - add_dependencies(CppStreamClient opencv_highgui opencv_videoio) + add_dependencies(CppVideoStreamClient opencv_highgui opencv_videoio) endif() -set_property(TARGET CppStreamClient PROPERTY CXX_STANDARD 17) +set_property(TARGET CppVideoStreamClient PROPERTY CXX_STANDARD 17) diff --git a/examples/cpp-video-stream-client/README.md b/examples/cpp-video-stream-client/README.md new file mode 100644 index 00000000..f0980816 --- /dev/null +++ b/examples/cpp-video-stream-client/README.md @@ -0,0 +1,18 @@ +# cpp-video-stream-client + +This example shows how to use the C++ library to create a client that sends and receives a video stream and an audio stream. +The video source is a video file and the audio source is a sin wave. +This example should be used with [web-stream-client](../web-stream-client). + +## How to use + +```bash +cd ../.. +mkdir build +cd build +cmake .. +cmake --build . --config Release|Debug + +cd bin/Release +./CppVideoStreamClient video_path +``` diff --git a/examples/cpp-stream-client/main.cpp b/examples/cpp-video-stream-client/main.cpp similarity index 99% rename from examples/cpp-stream-client/main.cpp rename to examples/cpp-video-stream-client/main.cpp index 5c35acb5..96882644 100644 --- a/examples/cpp-stream-client/main.cpp +++ b/examples/cpp-video-stream-client/main.cpp @@ -45,7 +45,7 @@ class CvVideoCaptureVideoSource : public VideoSource cap.open(m_path); if (!cap.isOpened()) { - cout << "Invalid video file" << endl; + cerr << "Invalid video file" << endl; exit(EXIT_FAILURE); }