整理代码

This commit is contained in:
蒲爽 2026-09-28 21:33:04 +08:00
commit c7b4e178ec
364 changed files with 59691 additions and 0 deletions

139
video/CMakeLists.txt Normal file
View file

@ -0,0 +1,139 @@
set(target video)
add_compile_definitions(
# 0d8c:0016: 板载声卡
-DBOARD_SOUND_CARD_ID="USB0d8c:0016"
)
aux_source_directory(. VIDEO_SOURCES)
aux_source_directory(core CORE_SOURCES)
list(APPEND VIDEO_SOURCES ${CORE_SOURCES})
aux_source_directory(v4l2 CORE_V4L2_SOURCES)
list(APPEND VIDEO_SOURCES ${CORE_V4L2_SOURCES})
aux_source_directory(alsa ALSA_SOURCES)
list(APPEND VIDEO_SOURCES ${ALSA_SOURCES})
if (USING_HISIMPP5)
aux_source_directory(hisi/mpp5 HISI_SOURCES)
aux_source_directory(hisi/mpp5/audio-adp HISI_ADP_SOURCES)
list(APPEND VIDEO_SOURCES ${HISI_SOURCES})
list(APPEND VIDEO_SOURCES ${HISI_ADP_SOURCES})
endif()
if (USING_ITE_FRONTEND)
aux_source_directory(ite ITE_SOURCES)
list(APPEND VIDEO_SOURCES ${ITE_SOURCES})
endif()
if (USING_LIBOPUS)
aux_source_directory(libopus LIBOPUS_SOURCES)
list(APPEND VIDEO_SOURCES ${LIBOPUS_SOURCES})
endif()
if (USING_SHINE)
aux_source_directory(shine SHINE_SOURCES)
list(APPEND VIDEO_SOURCES ${SHINE_SOURCES})
endif()
aux_source_directory(ffmpeg FFMPEG_SOURCES)
list(APPEND VIDEO_SOURCES ${FFMPEG_SOURCES})
aux_source_directory(user/alsa ALSA_USER_SOURCES)
list(APPEND VIDEO_SOURCES ${ALSA_USER_SOURCES})
aux_source_directory(mediaserver MEDIA_SERVER_USER_SOURCES)
list(APPEND VIDEO_SOURCES ${MEDIA_SERVER_USER_SOURCES})
aux_source_directory(user USER_SOURCES)
list(APPEND CMAKE_PREFIX_PATH $ENV{ALSALIB_PATH})
find_package(ALSA REQUIRED)
include_directories(
$ENV{MPP_PATH}/include
$ENV{FFMPEG_PATH}/include
$ENV{CIMG_PATH}
$ENV{LIBJPEG_PATH}/include
$ENV{LIBPNG_PATH}/include
$ENV{FREETYPE_PATH}/include/freetype2
$ENV{OSIP_PATH}/include
$ENV{EXOSIP_PATH}/include
$ENV{UCHARDET_PATH}/include
$ENV{LIBOPUS_PATH}/include
$ENV{SHINE_PATH}/include
$ENV{MEDIA_SERVER_PATH}/include
$ENV{MEDIA_SERVER_PATH}/include/avcodec/h264
${ALSA_INCLUDE_DIR}
)
add_library(${target} STATIC
${VIDEO_SOURCES}
${USER_SOURCES}
)
add_definitions(
-Dcimg_display=0
-Dcimg_use_jpeg
-Dcimg_use_png
)
target_link_libraries(${target} PRIVATE
$ENV{LIBJPEG_PATH}/lib/libjpeg.a
$ENV{LIBPNG_PATH}/lib/libpng.a
$ENV{FREETYPE_PATH}/lib/libfreetype.a
$ENV{EXOSIP_PATH}/lib/libeXosip2.a
$ENV{OSIP_PATH}/lib/libosip2.a
$ENV{OSIP_PATH}/lib/libosipparser2.a
$ENV{UCHARDET_PATH}/lib/libuchardet.a
$ENV{OPENJPEG_PATH}/lib/libopenjp2.a
${ALSA_LIBRARIES}
)
if (USING_ZLMEDIAKIT)
message("Using ZLMediaKit...")
add_subdirectory(zlmediakit)
add_definitions(-DUSING_ZLMEDIAKIT)
target_include_directories(${target} PUBLIC zlmediakit)
target_link_libraries(${target} PRIVATE zlmediakit)
endif()
if (USING_FFMPEG)
add_compile_definitions(-DUSING_FFMPEG -D__STDC_CONSTANT_MACROS)
target_link_libraries(${target} PRIVATE
$ENV{FFMPEG_PATH}/lib/libavdevice.a
$ENV{FFMPEG_PATH}/lib/libavfilter.a
$ENV{FFMPEG_PATH}/lib/libavformat.a
$ENV{FFMPEG_PATH}/lib/libavcodec.a
$ENV{FFMPEG_PATH}/lib/libavutil.a
$ENV{FFMPEG_PATH}/lib/libswscale.a
$ENV{FFMPEG_PATH}/lib/libswresample.a
$ENV{X264_PATH}/lib/libx264.a
$ENV{X265_PATH}/lib/libx265.a
$ENV{LIBMP3LAME_PATH}/lib/libmp3lame.a
dl
)
endif()
if (USING_MEDIASERVER)
target_link_libraries(${target} PRIVATE
$ENV{MEDIA_SERVER_PATH}/librtmp.a
$ENV{MEDIA_SERVER_PATH}/libflv.a
$ENV{MEDIA_SERVER_PATH}/libmpeg.a
$ENV{MEDIA_SERVER_PATH}/libh264.a
$ENV{MEDIA_SERVER_PATH}/libh265.a
)
endif()
if (USING_LIBOPUS)
target_link_libraries(${target} PRIVATE
$ENV{LIBOPUS_PATH}/lib/libopus.a
)
endif()
if (USING_SHINE)
target_link_libraries(${target} PRIVATE
$ENV{SHINE_PATH}/lib/libshine.a
)
endif()

179
video/VideoSubsystem.cpp Normal file
View file

@ -0,0 +1,179 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Logger.h"
#include "VideoSubsystem.h"
#include "core/Demuxer.h"
#include "core/AudioDecoder.h"
#include "core/AudioMixer.h"
#include "v4l2/Input.h"
#include "alsa/Output.h"
#include "libopus/AudioEncoder.h"
#include "libopus/AudioDecodeHandler.h"
#include "shine/AudioEncoder.h"
#include "hisi/mpp5/System.h"
#include "hisi/mpp5/VideoInput.h"
#include "hisi/mpp5/VideoEncoder.h"
#include "hisi/mpp5/VideoDecoder.h"
#include "hisi/mpp5/VideoProcessor.h"
#include "hisi/mpp5/VideoMixer.h"
#include "hisi/mpp5/VideoOverlay.h"
#include "hisi/mpp5/AudioEncoder.h"
#include "hisi/mpp5/HDMIAudioOutput.h"
#include "hisi/mpp5/HDMIVideoOutput.h"
#if defined(USING_FFMPEG)
#include "ffmpeg/Muxer.h"
#include "ffmpeg/DemuxHandler.h"
#include "ffmpeg/AudioEncoder.h"
#include "ffmpeg/AudioDecodeHandler.h"
#include "ffmpeg/AudioResample.h"
extern "C"
{
#include <libavutil/log.h>
}
#endif
#include "mediaserver/RTMPMuxer.h"
#include "mediaserver/MPEGMuxer.h"
#include "mediaserver/RTMPDemuxHandler.h"
#include <sdk/include/sockutil.h>
namespace Video
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoSubsystem::VideoSubsystem()
{
}
VideoSubsystem::~VideoSubsystem()
{
}
const char* VideoSubsystem::name() const
{
return "Video";
}
void VideoSubsystem::initialize(Poco::Util::Application& app)
{
socket_init();
Core::System::registerClass<MPP5::System>();
Core::VideoEncoder::registerClass<MPP5::VideoEncoder>(Core::CodecID::H264);
Core::VideoEncoder::registerClass<MPP5::VideoEncoder>(Core::CodecID::H265);
Core::VideoDecoder::registerClass<MPP5::VideoDecoder>(0);
Core::VideoProcessor::registerClass<MPP5::VideoProcessor>(0);
Core::VideoMixer::registerClass<MPP5::VideoMixer>(0);
Core::VideoOverlay::registerClass<MPP5::VideoOverlay>(Core::VideoOverlayType::CANVAS);
Core::VideoOverlay::registerClass<MPP5::VideoOverlay>(Core::VideoOverlayType::MOSAIC);
Core::VideoOutput::registerClass<MPP5::HDMIVideoOutput>("HDMI");
Core::AudioEncoder::registerClass<MPP5::AudioEncoder>(Core::CodecID::AAC);
Core::AudioDecoder::registerClass<Core::AudioDecoder>(0);
Core::AudioMixer::registerClass<Core::AudioMixer>(0);
Core::AudioOutput::registerClass<ALSA::Output>("ALSA");
Core::AudioOutput::registerClass<MPP5::HDMIAudioOutput>("HDMI");
Core::AudioEncoder::registerClass<LibOPUS::AudioEncoder>(Core::CodecID::OPUS);
Core::AudioDecodeHandler::registerClass<LibOPUS::AudioDecodeHandler>(Core::CodecID::OPUS);
Core::AudioEncoder::registerClass<Shine::AudioEncoder>(Core::CodecID::MP3);
#if defined(USING_FFMPEG)
Core::Muxer::registerClass<FFmpeg::Muxer>("rtsp");
Core::DemuxHandler::registerClass<MediaServer::RTMPDemuxHandler>("rtmp");
Core::DemuxHandler::registerClass<FFmpeg::DemuxHandler>("rtsp");
Core::DemuxHandler::registerClass<FFmpeg::DemuxHandler>("file");
Core::AudioEncoder::registerClass<FFmpeg::AudioEncoder>(Core::CodecID::G711A);
Core::AudioEncoder::registerClass<FFmpeg::AudioEncoder>(Core::CodecID::G711U);
Core::AudioResample::registerClass<FFmpeg::AudioResample>(0);
Core::AudioDecodeHandler::registerClass<FFmpeg::AudioDecodeHandler>(Core::CodecID::AAC);
Core::AudioDecodeHandler::registerClass<FFmpeg::AudioDecodeHandler>(Core::CodecID::G711A);
Core::AudioDecodeHandler::registerClass<FFmpeg::AudioDecodeHandler>(Core::CodecID::G711U);
#endif
Core::Muxer::registerClass<MediaServer::RTMPMuxer>("rtmp");
Core::Muxer::registerClass<MediaServer::MPEGMuxer>("udp");
Core::DemuxHandler::registerClass<MediaServer::RTMPDemuxHandler>("rtmp");
MPP5::VideoInput::initModule();
MPP5::VideoEncoder::initModule();
MPP5::VideoDecoder::initModule();
MPP5::AudioEncoder::initModule();
}
void VideoSubsystem::uninitialize()
{
stopAll();
#ifdef USING_ONVIF
ONVIF::ONVIFService::getIns()->stop();
#endif
MPP5::AudioEncoder::deinitModule();
MPP5::VideoInput::deinitModule();
MPP5::VideoEncoder::deinitModule();
socket_cleanup();
}
#if defined(USING_FFMPEG)
void VideoSubsystem::ffmpegLogCallback(void* ptr, int level, const char* fmt, va_list args)
{
if (level > av_log_get_level())
return;
char str[4096];
int len = sizeof(str);
int n = vsnprintf(str, len, fmt, args);
if (n >= len)
n = len - 1;
switch (level)
{
case AV_LOG_TRACE:
logger().trace("FFMPEG: " + std::string(str, n));
break;
case AV_LOG_VERBOSE:
case AV_LOG_DEBUG:
logger().debug("FFMPEG: " + std::string(str, n));
break;
case AV_LOG_INFO:
logger().information("FFMPEG: " + std::string(str, n));
break;
case AV_LOG_WARNING:
logger().warning("FFMPEG: " + std::string(str, n));
break;
case AV_LOG_ERROR:
logger().error("FFMPEG: " + std::string(str, n));
break;
case AV_LOG_FATAL:
logger().fatal("FFMPEG: " + std::string(str, n));
break;
default:
break;
}
}
#endif
}

41
video/VideoSubsystem.h Normal file
View file

@ -0,0 +1,41 @@
#ifndef __VIDEO_SUBSYSTEM__H__
#define __VIDEO_SUBSYSTEM__H__
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Poco/Util/Application.h"
#include "Poco/Util/Subsystem.h"
#include "Application.h"
#include "util/Subsystem.h"
#include <stdarg.h>
namespace Video
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class VideoSubsystem: public Util::Subsystem
{
public:
VideoSubsystem();
~VideoSubsystem();
virtual const char* name() const;
protected:
virtual void initialize(Poco::Util::Application& app) override;
virtual void uninitialize();
protected:
#if defined(USING_FFMPEG)
static void ffmpegLogCallback(void* ptr, int level, const char* fmt, va_list vl);
#endif
private:
};
}
#endif

144
video/alsa/Input.cpp Normal file
View file

@ -0,0 +1,144 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Input.h"
#include "Util.h"
#include "../core/System.h"
#include "Logger.h"
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Input::Input(const std::string& name): Core::AbstractAudioInput(name)
{
setSoundCard(nullptr);
}
Input::~Input()
{
}
void Input::start()
{
_established = tryEstablishSoundCard();
run();
}
void Input::stop()
{
cancel();
if (_established)
{
_pcm.close();
_established = false;
}
}
void Input::onConfigChanged(ParameterNames& names)
{
if (!started())
return;
stop();
start();
AbstractAudioInput::onConfigChanged(names);
}
Core::Frame::Ptr Input::readFrame()
{
if (!_established)
_established = tryEstablishSoundCard();
if (!_established)
{
_traffic = 0;
return nullptr;
}
Core::Frame::Ptr pFrame = new Core::Frame;
pFrame->length = _linesize;
pFrame->timestamp = Core::System::timestamp();
int rc;
if (SND_PCM_ACCESS_RW_NONINTERLEAVED == _access)
{
uint8_t* ptrs[_channels];
for (int i = 0; i < _channels; i++)
{
pFrame->data[i] = new uint8_t[_linesize];
ptrs[i] = pFrame->data[i].get();
}
rc = _pcm.read(ptrs, _samples);
}
else
{
pFrame->data[0] = new uint8_t[_linesize];
uint8_t* ptr = pFrame->data[0].get();
rc = _pcm.read(ptr, _samples);
}
if (rc < 0)
{
_established = false;
_pcm.close();
_traffic = 0;
return nullptr;
}
_traffic++;
return pFrame;
}
bool Input::tryEstablishSoundCard()
{
auto pCard = getSoundCard();
if (pCard)
{
int rc = _pcm.open(pCard, SND_PCM_STREAM_CAPTURE);
if (rc >= 0)
{
const auto sampleRate = Util::getSuitableSampleRate(pCard, getSampleRate());
const auto access = Util::getSuitableAccess(pCard, sampleFormatIsPlanar(getSampleFormat()));
const auto format = Util::getSuitableSampleFormat(pCard, getSampleFormat());
const auto sampleFormat = Util::convertSampleFormat(format, access == SND_PCM_ACCESS_RW_INTERLEAVED);
logger().debug("%s: Suitable config found: %s %s %uHz", name(), std::string(snd_pcm_access_name(access)),
std::string(snd_pcm_format_name(format)), sampleRate);
rc = _pcm.config(access, format, pCard->info().channels, sampleRate, getFrameSize(),
sampleFormatBytesPerSample(sampleFormat));
if (rc >= 0)
{
if (pCard->info().channels == 1 && getChannelLayout() != Core::ChannelLayout::MONO)
setChannelLayout(Core::ChannelLayout::MONO);
setSampleRate(sampleRate);
setSampleFormat(sampleFormat);
_linesize = sampleFormatCalcLineSize(sampleFormat, getChannelLayout(), getFrameSize());
_channels = channelLayoutChannels(getChannelLayout());
_samples = getFrameSize();
_access = access;
serialCodeManager().increment();
return true;
}
_pcm.close();
}
}
return false;
}
}
}

53
video/alsa/Input.h Normal file
View file

@ -0,0 +1,53 @@
#ifndef C0535B0C_9BD8_4FDC_9429_8FEF7E58BFCE
#define C0535B0C_9BD8_4FDC_9429_8FEF7E58BFCE
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/AbstractAudioInput.h"
#include "SoundCard.h"
#include "PCM.h"
#include "util/SimpleFactory.h"
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Input: public Core::AbstractAudioInput, public ::Util::SimpleFactory<Input, const std::string&>
{
public:
using Ptr = Poco::AutoPtr<Input>;
public:
Input(const std::string& name);
~Input();
public:
CONFIGURABLE_DECLARE_PARAMETER(SoundCard, SoundCard::Ptr);
protected:
void start() override;
void stop() override;
void onConfigChanged(ParameterNames& names) override;
Core::Frame::Ptr readFrame() override;
bool tryEstablishSoundCard();
private:
PCM _pcm;
std::atomic<bool> _established;
int _linesize;
int _channels;
int _samples;
snd_pcm_access_t _access;
};
}
}
#endif

162
video/alsa/Output.cpp Normal file
View file

@ -0,0 +1,162 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Output.h"
#include "PCM.h"
#include "SoundCard.h"
#include "Util.h"
#include "Logger.h"
#include "../core/System.h"
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Output::Output(const std::string& arg): Core::AudioOutput("ALSA::Output(" + arg + ")")
{
auto pos = arg.find(':');
_soundCardID = (pos != std::string::npos) ? arg.substr(pos + 1) : arg;
}
Output::~Output()
{
}
void Output::initialize()
{
setSoundCardEstablished(tryEstablishSoundCard());
if (!getSoundCardEstablished())
runSearchTask();
}
void Output::deinitialize()
{
_searchTask.cancel();
if (getSoundCardEstablished())
_pcm.close();
}
void Output::onConfigChanged(ParameterNames& names)
{
}
void Output::writeFrame(Core::Frame::Ptr pFrame)
{
if (!getSoundCardEstablished())
return;
int rc;
if (SND_PCM_ACCESS_RW_NONINTERLEAVED == _access)
{
uint8_t* ptrs[_channels];
for (int i = 0; i < _channels; i++)
ptrs[i] = pFrame->data[i].get();
rc = _pcm.write(ptrs, _samples);
}
else
{
rc = _pcm.write(pFrame->data[0].get(), _samples);
}
if (rc < 0)
{
setSoundCardEstablished(false);
_pcm.close();
}
}
SoundCard::Ptr Output::getSoundCard()
{
auto cards = SoundCard::getSoundCards([this](SoundCard::Ptr pCard) -> bool
{ return (pCard->info().components == _soundCardID); });
if (cards.size() > 0)
return *(cards.begin());
return nullptr;
}
bool Output::tryEstablishSoundCard()
{
try
{
auto pCard = getSoundCard();
if (pCard)
{
int rc = _pcm.open(pCard, SND_PCM_STREAM_PLAYBACK);
if (rc >= 0)
{
const auto sampleRate = Util::getSuitableSampleRate(pCard, getSampleRate());
const auto access = Util::getSuitableAccess(pCard, sampleFormatIsPlanar(getSampleFormat()));
const auto format = Util::getSuitableSampleFormat(pCard, getSampleFormat());
const auto sampleFormat = Util::convertSampleFormat(format, access == SND_PCM_ACCESS_RW_INTERLEAVED);
logger().debug("%s: Suitable config found: %s %s %uHz", name(),
std::string(snd_pcm_access_name(access)), std::string(snd_pcm_format_name(format)),
sampleRate);
rc = _pcm.config(access, format, pCard->info().channels, sampleRate, getFrameSize(),
sampleFormatBytesPerSample(sampleFormat));
if (rc >= 0)
{
if (pCard->info().channels == 1 && getChannelLayout() != Core::ChannelLayout::MONO)
setChannelLayout(Core::ChannelLayout::MONO);
setSampleRate(sampleRate);
setSampleFormat(sampleFormat);
_channels = channelLayoutChannels(getChannelLayout());
_samples = getFrameSize();
_access = access;
serialCodeManager().increment();
return true;
}
_pcm.close();
}
}
}
catch (Poco::Exception& e)
{
logger().error("%s: %s", name(), e.displayText());
}
catch (std::exception& e)
{
logger().error("%s: %s", name(), std::string(e.what()));
}
return false;
}
void Output::runSearchTask()
{
if (_searchTask.isRunning())
return;
_searchTask.run(
[this](std::atomic<bool>& isCancelled) -> void
{
while (!isCancelled)
{
if (tryEstablishSoundCard())
{
setSoundCardEstablished(true);
break;
}
Poco::Thread::sleep(2000);
}
});
}
}
}

50
video/alsa/Output.h Normal file
View file

@ -0,0 +1,50 @@
#ifndef A7586F47_A0A8_4B55_9701_8E79240D6736
#define A7586F47_A0A8_4B55_9701_8E79240D6736
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/AudioOutput.h"
#include "PCM.h"
#include "SoundCard.h"
#include "util/Task.h"
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Output: public Core::AudioOutput
{
public:
Output(const std::string& arg);
~Output();
protected:
void initialize() override;
void deinitialize() override;
void onConfigChanged(ParameterNames& names) override;
void writeFrame(Core::Frame::Ptr pFrame) override;
SoundCard::Ptr getSoundCard();
bool tryEstablishSoundCard();
void runSearchTask();
PROPERTY_DECLARE(SoundCardEstablished, bool, false, protected, protected)
private:
std::string _soundCardID;
::Util::Task _searchTask;
PCM _pcm;
snd_pcm_access_t _access;
int _samples;
int _channels;
};
}
}
#endif

144
video/alsa/PCM.cpp Normal file
View file

@ -0,0 +1,144 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "PCM.h"
#include <alsa/asoundlib.h>
#include <iostream>
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
PCM::PCM()
{
}
PCM::~PCM()
{
}
int PCM::open(SoundCard::Ptr pSoundCard, snd_pcm_stream_t stream)
{
_stream = stream;
return snd_pcm_open((snd_pcm_t**)&_pPCM, pSoundCard->info().device.c_str(), stream, 0);
}
int PCM::close()
{
snd_pcm_drain((snd_pcm_t*)_pPCM);
snd_pcm_close((snd_pcm_t*)_pPCM);
return 0;
}
int PCM::config(snd_pcm_access_t access, snd_pcm_format_t format, int channels, int rate, int framesz, int samplesz)
{
#define __check_error(x) \
rc = x; \
if (rc < 0) \
{ \
std::cout << "Error: " << snd_strerror(rc) << "@" << #x << std::endl; \
goto out; \
}
snd_pcm_hw_params_t* hwParams = nullptr;
snd_pcm_sw_params_t* swParams = nullptr;
int rc;
__check_error(snd_pcm_hw_params_malloc(&hwParams));
__check_error(snd_pcm_hw_params_any((snd_pcm_t*)_pPCM, hwParams));
__check_error(snd_pcm_hw_params_set_access((snd_pcm_t*)_pPCM, hwParams, access));
__check_error(snd_pcm_hw_params_set_format((snd_pcm_t*)_pPCM, hwParams, format));
__check_error(snd_pcm_hw_params_set_channels((snd_pcm_t*)_pPCM, hwParams, channels));
__check_error(snd_pcm_hw_params_set_rate((snd_pcm_t*)_pPCM, hwParams, rate, 0));
__check_error(snd_pcm_hw_params_set_period_size((snd_pcm_t*)_pPCM, hwParams, framesz, 0));
__check_error(snd_pcm_hw_params_set_periods((snd_pcm_t*)_pPCM, hwParams, samplesz, 0));
__check_error(snd_pcm_hw_params_set_buffer_size((snd_pcm_t*)_pPCM, hwParams, samplesz * framesz));
__check_error(snd_pcm_hw_params((snd_pcm_t*)_pPCM, hwParams));
if (_stream == SND_PCM_STREAM_PLAYBACK)
{
__check_error(snd_pcm_sw_params_malloc(&swParams));
__check_error(snd_pcm_sw_params_current((snd_pcm_t*)_pPCM, swParams));
__check_error(snd_pcm_sw_params_set_avail_min((snd_pcm_t*)_pPCM, swParams, framesz));
__check_error(snd_pcm_sw_params_set_start_threshold((snd_pcm_t*)_pPCM, swParams, framesz * samplesz));
__check_error(snd_pcm_sw_params((snd_pcm_t*)_pPCM, swParams));
}
out:
if (hwParams)
snd_pcm_hw_params_free(hwParams);
if (swParams)
snd_pcm_sw_params_free(swParams);
return rc;
}
int PCM::write(const void* data, uint32_t framesz)
{
int rc = snd_pcm_writei((snd_pcm_t*)_pPCM, data, framesz);
if (rc < 0)
{
if (rc == -EPIPE)
rc = snd_pcm_prepare((snd_pcm_t*)_pPCM);
}
return rc;
}
int PCM::write(void** datas, uint32_t framesz)
{
int rc = snd_pcm_writen((snd_pcm_t*)_pPCM, datas, framesz);
if (rc < 0)
{
if (rc == -EPIPE)
rc = snd_pcm_prepare((snd_pcm_t*)_pPCM);
}
return rc;
}
int PCM::read(void* buffer, uint32_t framesz)
{
int rc = snd_pcm_readi((snd_pcm_t*)_pPCM, buffer, (snd_pcm_uframes_t)framesz);
if (rc < 0)
{
std::cout << "ALSA error: " << rc << " -> " << snd_strerror(rc) << "@" << framesz << std::endl;
if (rc == -EPIPE)
rc = snd_pcm_prepare((snd_pcm_t*)_pPCM);
}
return rc;
}
int PCM::read(void** buffers, uint32_t framesz)
{
int rc = snd_pcm_readn((snd_pcm_t*)_pPCM, buffers, (snd_pcm_uframes_t)framesz);
if (rc < 0)
{
std::cout << "ALSA error: " << rc << " -> " << snd_strerror(rc) << "@" << framesz << std::endl;
if (rc == -EPIPE)
rc = snd_pcm_prepare((snd_pcm_t*)_pPCM);
}
return rc;
}
}
}

43
video/alsa/PCM.h Normal file
View file

@ -0,0 +1,43 @@
#ifndef F1145346_5CCE_48F8_8398_073AFB8758C6
#define F1145346_5CCE_48F8_8398_073AFB8758C6
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "SoundCard.h"
#include <alsa/asoundlib.h>
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class PCM
{
public:
PCM();
~PCM();
public:
int open(SoundCard::Ptr pSoundCard, snd_pcm_stream_t st);
int close();
int config(snd_pcm_access_t access, snd_pcm_format_t format, int channels, int rate, int framesz, int samplesz);
int write(const void* data, uint32_t framesz);
int write(void** datas, uint32_t framesz);
int read(void* buffer, uint32_t framesz);
int read(void** buffers, uint32_t framesz);
private:
void* _pPCM;
snd_pcm_stream_t _stream;
};
}
}
#endif

124
video/alsa/SoundCard.cpp Normal file
View file

@ -0,0 +1,124 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "SoundCard.h"
#include <Poco/Format.h>
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
SoundCard::SoundCard(int index): _index(index)
{
initialize();
}
SoundCard::~SoundCard()
{
}
void SoundCard::initialize()
{
_info.device = Poco::format("hw:%d", _index);
int rc;
snd_ctl_t* handle;
if ((rc = snd_ctl_open(&handle, _info.device.c_str(), 0)) < 0)
throw Poco::InvalidAccessException("SoundCard: Cannot open device");
snd_ctl_card_info_t* info;
snd_ctl_card_info_alloca(&info);
if ((rc = snd_ctl_card_info(handle, info)) < 0)
throw Poco::InvalidAccessException("SoundCard: Cannot get card info");
_info.id = snd_ctl_card_info_get_id(info);
_info.name = snd_ctl_card_info_get_name(info);
_info.longname = snd_ctl_card_info_get_longname(info);
_info.driver = snd_ctl_card_info_get_driver(info);
_info.components = snd_ctl_card_info_get_components(info);
_info.mixername = snd_ctl_card_info_get_mixername(info);
snd_ctl_close(handle);
auto enumerateCapabilities = [](snd_pcm_t* pcm, Info& info)
{
snd_pcm_hw_params_t* params;
snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(pcm, params);
for (int access = 0; access <= SND_PCM_ACCESS_LAST; access++)
info.access[access] = (snd_pcm_hw_params_test_access(pcm, params, (snd_pcm_access_t)access) == 0);
for (int format = 0; format <= SND_PCM_FORMAT_LAST; format++)
{
if (snd_pcm_hw_params_test_format(pcm, params, (snd_pcm_format_t)format) == 0)
info.formats.push_back((snd_pcm_format_t)format);
}
for (unsigned int rate = 1000; rate <= 384000; rate += 1000)
{
if (snd_pcm_hw_params_test_rate(pcm, params, rate, 0) == 0)
info.sampleRates.push_back(rate);
}
for (int channels = 1; channels <= 16; channels++)
{
if (snd_pcm_hw_params_test_channels(pcm, params, channels) == 0)
{
info.channels = channels;
break;
}
}
for (snd_pcm_uframes_t val = 16; val <= 1048576; val *= 2)
{
if (snd_pcm_hw_params_test_buffer_size(pcm, params, val) == 0)
info.bufferSizes.push_back(val);
}
};
snd_pcm_t* pcm;
int err = snd_pcm_open(&pcm, _info.device.c_str(), SND_PCM_STREAM_CAPTURE, 0);
if (err < 0)
_info.supportCapture = false;
else
{
_info.supportCapture = true;
enumerateCapabilities(pcm, _info);
snd_pcm_close(pcm);
}
err = snd_pcm_open(&pcm, _info.device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0)
_info.supportPlayback = false;
else
{
_info.supportPlayback = true;
if (!_info.supportCapture)
enumerateCapabilities(pcm, _info);
snd_pcm_close(pcm);
}
}
SoundCard::Info& SoundCard::info()
{
return _info;
}
SoundCard::SoundCards SoundCard::getSoundCards()
{
SoundCards cards;
int id = -1;
while (snd_card_next(&id), id >= 0)
cards.push_back(new SoundCard(id));
return cards;
}
}
}

83
video/alsa/SoundCard.h Normal file
View file

@ -0,0 +1,83 @@
#ifndef A83739DB_6BC9_4B7C_8FA0_11DC60C05AA3
#define A83739DB_6BC9_4B7C_8FA0_11DC60C05AA3
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include <alsa/asoundlib.h>
#include <Poco/SharedPtr.h>
#include <string>
#include <vector>
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class SoundCard
{
public:
struct Info
{
std::string id;
std::string device;
std::string name;
std::string longname;
std::string driver;
std::string components;
std::string mixername;
bool access[SND_PCM_ACCESS_LAST + 1];
std::vector<snd_pcm_format_t> formats;
int channels;
std::vector<uint32_t> sampleRates;
std::vector<uint32_t> bufferSizes;
bool supportCapture;
bool supportPlayback;
bool operator==(const Info& other)
{
return components == other.components;
}
};
using Ptr = Poco::SharedPtr<SoundCard>;
using SoundCards = std::vector<Ptr>;
private:
SoundCard(int index);
void initialize();
public:
~SoundCard();
Info& info();
private:
int _index;
Info _info;
public:
static SoundCards getSoundCards();
template <class F> static SoundCards getSoundCards(F&& filter);
};
template <class F> inline SoundCard::SoundCards SoundCard::getSoundCards(F&& filter)
{
SoundCard::SoundCards cards;
for (auto& card : ALSA::SoundCard::getSoundCards())
{
if (filter(card))
cards.push_back(card);
}
return cards;
}
}
}
#endif

154
video/alsa/Util.cpp Normal file
View file

@ -0,0 +1,154 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Util.h"
#include "Logger.h"
#include <cstdint>
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
uint32_t Util::getSuitableSampleRate(SoundCard::Ptr pCard, uint32_t target)
{
uint32_t suitable = target;
if (!pCard->info().sampleRates.empty())
{
for (auto sr : pCard->info().sampleRates)
{
if (sr % target == 0 || target % sr == 0)
{
suitable = sr;
goto out;
}
}
int minDiff = __INT32_MAX__;
for (auto sr : pCard->info().sampleRates)
{
int diff = std::abs(static_cast<int>(sr - target));
if (diff < minDiff || (diff == minDiff && sr > suitable))
{
minDiff = diff;
suitable = sr;
}
}
}
out:
return suitable;
}
snd_pcm_format_t Util::getSuitableSampleFormat(SoundCard::Ptr pCard, Core::SampleFormat refer)
{
auto target = convertSampleFormat(refer);
if (pCard->info().formats.empty())
return SND_PCM_FORMAT_S16_LE;
for (auto& fmt : pCard->info().formats)
{
if (fmt == (int)target)
return target;
}
target = *(pCard->info().formats.begin());
return target;
}
snd_pcm_access_t Util::getSuitableAccess(SoundCard::Ptr pCard, bool planar)
{
if (planar && (pCard->info().access[SND_PCM_ACCESS_RW_NONINTERLEAVED]))
return SND_PCM_ACCESS_RW_NONINTERLEAVED;
return SND_PCM_ACCESS_RW_INTERLEAVED;
}
snd_pcm_format_t Util::convertSampleFormat(Core::SampleFormat format)
{
switch (sampleFormatGetPlanarFormat(format))
{
case Core::SampleFormat::U8P:
return SND_PCM_FORMAT_U8;
case Core::SampleFormat::S16P:
return SND_PCM_FORMAT_S16_LE;
case Core::SampleFormat::S32P:
return SND_PCM_FORMAT_S32_LE;
case Core::SampleFormat::FLTP:
return SND_PCM_FORMAT_FLOAT_LE;
case Core::SampleFormat::DBLP:
return SND_PCM_FORMAT_FLOAT64_LE;
default:
return SND_PCM_FORMAT_S16_LE;
}
}
Core::SampleFormat Util::convertSampleFormat(snd_pcm_format_t format, bool interleaved)
{
Core::SampleFormat result;
switch (format)
{
case SND_PCM_FORMAT_U8:
result = Core::SampleFormat::U8P;
break;
case SND_PCM_FORMAT_S8:
result = Core::SampleFormat::U8P;
break;
case SND_PCM_FORMAT_S16_LE:
result = Core::SampleFormat::S16P;
break;
case SND_PCM_FORMAT_S16_BE:
result = Core::SampleFormat::S16P;
break;
case SND_PCM_FORMAT_S32_LE:
result = Core::SampleFormat::S32P;
break;
case SND_PCM_FORMAT_S32_BE:
result = Core::SampleFormat::S32P;
break;
case SND_PCM_FORMAT_FLOAT_LE:
result = Core::SampleFormat::FLTP;
break;
case SND_PCM_FORMAT_FLOAT_BE:
result = Core::SampleFormat::FLTP;
break;
case SND_PCM_FORMAT_FLOAT64_LE:
result = Core::SampleFormat::DBLP;
break;
case SND_PCM_FORMAT_FLOAT64_BE:
result = Core::SampleFormat::DBLP;
break;
case SND_PCM_FORMAT_SPECIAL:
case SND_PCM_FORMAT_S24_3LE:
case SND_PCM_FORMAT_S24_3BE:
case SND_PCM_FORMAT_U24_3LE:
case SND_PCM_FORMAT_U24_3BE:
case SND_PCM_FORMAT_S20_3LE:
case SND_PCM_FORMAT_S20_3BE:
case SND_PCM_FORMAT_U20_3LE:
case SND_PCM_FORMAT_U20_3BE:
case SND_PCM_FORMAT_S18_3LE:
case SND_PCM_FORMAT_S18_3BE:
case SND_PCM_FORMAT_U18_3LE:
default:
logger().warning(": Unsupported format: %s", std::string(snd_pcm_format_name(format)));
result = Core::SampleFormat::S32P;
break;
}
if (interleaved)
result = Core::sampleFormatGetPackedFormat(result);
return result;
}
}
}

31
video/alsa/Util.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef C0D42E11_A47F_4E62_820B_17AD3C7FE57C
#define C0D42E11_A47F_4E62_820B_17AD3C7FE57C
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "SoundCard.h"
#include "../core/SampleFormat.h"
namespace Video
{
namespace ALSA
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Util
{
public:
static uint32_t getSuitableSampleRate(SoundCard::Ptr pCard, uint32_t target);
static snd_pcm_format_t getSuitableSampleFormat(SoundCard::Ptr pCard, Core::SampleFormat refer);
static snd_pcm_access_t getSuitableAccess(SoundCard::Ptr pCard, bool planar);
static snd_pcm_format_t convertSampleFormat(Core::SampleFormat format);
static Core::SampleFormat convertSampleFormat(snd_pcm_format_t format, bool interleaved);
};
}
}
#endif

View file

@ -0,0 +1,284 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AbstractAudioInput.h"
#include "System.h"
#include "Logger.h"
#include "util/Math.h"
#include <atomic>
#include <climits>
#include <cmath>
#include <unistd.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AbstractAudioInput::AbstractAudioInput(const std::string& name)
: AudioComponent(name)
{
const auto len = 1024 * 32;
_pSilentData = new uint8_t[len];
memset(_pSilentData, 0x00, len);
_silentDataCode = serialCodeManager().code();
_pSilentFrame = new Frame;
for (int i = 0; i < Frame::DATA_LINES; i++)
_pSilentFrame->data[i] = _pSilentData;
}
AbstractAudioInput::~AbstractAudioInput()
{
}
void AbstractAudioInput::run()
{
updateSilentFrame();
_task.run(
[this](std::atomic<bool>& isCancelled) -> void
{
Util::Task probeTask;
std::atomic<bool> attached(true);
setSignalLost(false);
Frame::Ptr pFrame;
SerialCodeWatcher watcher;
uint64_t timeline = 0;
while (!isCancelled)
{
if (attached)
{
pFrame = tryReadFrame(true);
if (pFrame.isNull())
{
probeTask.run(
[&](std::atomic<bool>& isCancelled) -> void
{
while (!isCancelled)
{
if (!tryReadFrame(false).isNull())
{
attached = true;
setSignalLost(false);
break;
}
Poco::Thread::sleep(2000);
}
});
attached = false;
setSignalLost(true);
continue;
}
const int gain = _gain.load(std::memory_order_relaxed);
if (gain != 0)
applyGain(pFrame, gain);
const int64_t ptsOffset = _ptsOffsetUs.load(std::memory_order_relaxed);
if (ptsOffset != 0)
applyPTSOffset(pFrame, ptsOffset);
pFrame->code = serialCodeManager().code();
if (watcher.isChanged(pFrame->code, true))
{
updateSilentFrame();
}
}
else
{
pFrame = readSilent(false);
}
if (pFrame->timestamp < timeline)
pFrame->timestamp = timeline;
else
timeline = pFrame->timestamp;
if (_isMute)
pFrame = readSilent(true);
postToNextComponents(sharedFromThis(), pFrame, MediaType::AUDIO, isCancelled);
}
probeTask.cancel();
});
}
void AbstractAudioInput::cancel()
{
_task.cancel();
}
void AbstractAudioInput::onConfigChanged(ParameterNames& names)
{
updateSilentFrame();
if (names.exists(_Gain))
_gain.store(getGain(), std::memory_order_relaxed);
if (names.exists(_PTSOffset))
_ptsOffsetUs.store(getPTSOffset(), std::memory_order_relaxed);
}
Frame::Ptr AbstractAudioInput::tryReadFrame(bool shouldLog)
{
try
{
return readFrame();
}
catch (Poco::Exception& e)
{
if (shouldLog)
logger().error("%s: %s", name(), e.displayText());
}
catch (std::exception& e)
{
if (shouldLog)
logger().error("%s: %s", name(), std::string(e.what()));
}
return nullptr;
}
Frame::Ptr AbstractAudioInput::readSilent(bool freezeChannel)
{
uint64_t now = System::timestamp();
_pSilentFrame->timestamp = now;
_pSilentFrame->code = _silentDataCode;
if (!freezeChannel)
{
std::this_thread::sleep_for(std::chrono::microseconds(_duration));
}
return _pSilentFrame;
}
void AbstractAudioInput::updateSilentFrame()
{
_pSilentFrame->length = Core::sampleFormatCalcLineSize(getSampleFormat(), getChannelLayout(), getFrameSize());
_duration = (float)1000000 / getSampleRate() * getFrameSize() * 0.85;
_silentDataCode = serialCodeManager().code();
}
void AbstractAudioInput::applyGain(Frame::Ptr pFrame, int gain)
{
gain = ::Util::Math::clamp(gain, -50, 20);
if (gain == 0)
return;
const auto format = getSampleFormat();
if (format == SampleFormat::NONE)
return;
const uint32_t channels = (uint32_t)channelLayoutChannels(getChannelLayout());
const uint32_t samplesPerChannel = getFrameSize();
const double scale = std::pow(10.0, (double)gain / 20.0);
auto apply = [&](uint8_t* base, uint32_t n)
{
switch (format)
{
case SampleFormat::U8:
case SampleFormat::U8P:
{
auto p = (uint8_t*)base;
for (uint32_t i = 0; i < n; i++)
{
const int64_t v = (int64_t)(p[i] * scale + 0.5);
p[i] = (uint8_t)::Util::Math::clamp(v, (int64_t)0, (int64_t)255);
}
break;
}
case SampleFormat::S16:
case SampleFormat::S16P:
{
auto p = (int16_t*)base;
for (uint32_t i = 0; i < n; i++)
{
const int64_t v = (int64_t)(p[i] * scale + 0.5);
p[i] = (int16_t)::Util::Math::clamp(v, (int64_t)INT16_MIN, (int64_t)INT16_MAX);
}
break;
}
case SampleFormat::S32:
case SampleFormat::S32P:
{
auto p = (int32_t*)base;
for (uint32_t i = 0; i < n; i++)
{
const int64_t v = (int64_t)(p[i] * scale + 0.5);
p[i] = (int32_t)::Util::Math::clamp(v, (int64_t)INT32_MIN, (int64_t)INT32_MAX);
}
break;
}
case SampleFormat::S64:
case SampleFormat::S64P:
{
auto p = (int64_t*)base;
for (uint32_t i = 0; i < n; i++)
p[i] = (int64_t)(p[i] * scale);
break;
}
case SampleFormat::FLT:
case SampleFormat::FLTP:
{
auto p = (float*)base;
for (uint32_t i = 0; i < n; i++)
p[i] = (float)(p[i] * scale);
break;
}
case SampleFormat::DBL:
case SampleFormat::DBLP:
{
auto p = (double*)base;
for (uint32_t i = 0; i < n; i++)
p[i] *= scale;
break;
}
default:
break;
}
};
if (sampleFormatIsPlanar(format))
{
for (uint32_t ch = 0; ch < channels; ch++)
{
if (!pFrame->data[ch])
continue;
apply(pFrame->data[ch].get(), samplesPerChannel);
}
}
else
{
if (!pFrame->data[0])
return;
apply(pFrame->data[0].get(), samplesPerChannel * channels);
}
}
void AbstractAudioInput::applyPTSOffset(Frame::Ptr pFrame, int64_t ptsOffset)
{
if (ptsOffset != 0)
{
const int64_t ts = (int64_t)pFrame->timestamp + ptsOffset;
pFrame->timestamp = (ts > 0) ? (uint64_t)ts : 0ULL;
}
}
void AbstractAudioInput::reset()
{
}
}
}

View file

@ -0,0 +1,56 @@
#ifndef E2E4239C_C93E_478C_AD4C_D74AC3D4ED3E
#define E2E4239C_C93E_478C_AD4C_D74AC3D4ED3E
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioComponent.h"
#include "Frame.h"
#include "util/Task.h"
#include "util/Broadcast.h"
#include <atomic>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AbstractAudioInput: public AudioComponent
{
public:
AbstractAudioInput(const std::string& name);
~AbstractAudioInput();
void reset() override;
protected:
virtual Frame::Ptr readFrame() = 0;
void run();
void cancel();
void onConfigChanged(ParameterNames& names) override;
void applyGain(Frame::Ptr pFrame, int gain);
void applyPTSOffset(Frame::Ptr pFrame, int64_t ptsOffset);
private:
Frame::Ptr tryReadFrame(bool shuldLog = false);
Frame::Ptr readSilent(bool freezeChannel);
void updateSilentFrame();
private:
Util::Task _task;
Frame::Ptr _pSilentFrame;
Poco::SharedPtr<uint8_t> _pSilentData;
SerialCode _silentDataCode;
uint64_t _duration;
uint32_t _audioChannelId;
bool _isMute;
std::atomic<int> _gain{0};
std::atomic<int64_t> _ptsOffsetUs{0};
};
}
}
#endif

View file

@ -0,0 +1,83 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioAdaptablePipe.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
void AudioAdaptablePipe::onLink(Core::Component::Ptr pPrevious)
{
if (!frameQueues().empty() && frameQueues().firstComponent() != pPrevious)
throw Poco::LogicException(name() + ": Support only one previous component, conflict with: " +
pPrevious->name() + ", Current: " + frameQueues().firstComponent()->name());
frameQueues().add(pPrevious, name(), MediaType::AUDIO);
}
void AudioAdaptablePipe::onUnlink(Core::Component::Ptr pPrevious)
{
frameQueues().remove(pPrevious);
}
Frame::Ptr AudioAdaptablePipe::readFrame(uint32_t timeout)
{
if (previousComponents().size() == 0)
throw Poco::NotFoundException(name() + ": No previous components");
auto pFrameQueue = frameQueues().first();
while (true)
{
Frame::Ptr pFrame = nullptr;
if (!_pFrameAdapter.isNull())
{
pFrame = _pFrameAdapter->get();
if (!pFrame.isNull())
return pFrame;
}
pFrame = pFrameQueue->dequeue(timeout);
if (serialCodeWatcher().isChanged(pFrame->code, true))
{
auto pPrevious = previousComponents().first().cast<AudioComponent>();
auto inLayout = pPrevious->getChannelLayout();
auto inFormat = pPrevious->getSampleFormat();
auto inRate = pPrevious->getSampleRate();
auto outLayout = getChannelLayout();
auto outFormat = getSampleFormat();
auto outRate = getSampleRate();
if (inLayout != outLayout || inFormat != outFormat || inRate != outRate)
_pResample = AudioResample::create(inLayout, inFormat, inRate, outLayout, outFormat, outRate);
else
_pResample.reset();
if (inRate != outRate || pPrevious->getFrameSize() != getFrameSize())
_pFrameAdapter = new AudioFrameAdapter(outLayout, outFormat, getFrameSize(), outRate);
else
_pFrameAdapter.reset();
}
if (_pFrameAdapter.isNull() && _pResample.isNull())
return pFrame;
if (!_pResample.isNull())
pFrame = _pResample->process(pFrame);
if (!_pFrameAdapter.isNull())
_pFrameAdapter->put(pFrame);
else
return pFrame;
}
}
}
}

View file

@ -0,0 +1,38 @@
#ifndef BCE1B4DD_976E_456D_AE9A_87A17E300579
#define BCE1B4DD_976E_456D_AE9A_87A17E300579
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioComponent.h"
#include "AudioFrameAdapter.h"
#include "AudioResample.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioAdaptablePipe: public AudioComponent
{
public:
using AudioComponent::AudioComponent;
protected:
void onLink(Core::Component::Ptr pPrevious) override;
void onUnlink(Core::Component::Ptr pPrevious) override;
virtual Frame::Ptr readFrame(uint32_t timeout = 1000);
private:
AudioResample::Ptr _pResample;
AudioFrameAdapter::Ptr _pFrameAdapter;
};
}
}
#endif

View file

@ -0,0 +1,32 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioComponent.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioComponent::AudioComponent(const std::string& name): Component(name)
{
setSampleRate(48000);
setChannelLayout(ChannelLayout::STEREO);
setSampleFormat(SampleFormat::S16P);
setFrameSize(1024);
setGain(0);
setPTSOffset(0);
setSignalLost(true);
}
AudioComponent::~AudioComponent()
{
}
}
}

View file

@ -0,0 +1,43 @@
#ifndef D770D9A1_7014_4DBA_99C9_708BEDAA10F6
#define D770D9A1_7014_4DBA_99C9_708BEDAA10F6
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Component.h"
#include "CodecID.h"
#include "ChannelLayout.h"
#include "SampleFormat.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioComponent: public Component
{
public:
using Ptr = Poco::AutoPtr<AudioComponent>;
public:
AudioComponent(const std::string& name);
virtual ~AudioComponent();
virtual void reset() {};
public:
CONFIGURABLE_DECLARE_PARAMETER(SampleRate, int);
CONFIGURABLE_DECLARE_PARAMETER(ChannelLayout, ChannelLayout);
CONFIGURABLE_DECLARE_PARAMETER(SampleFormat, SampleFormat);
CONFIGURABLE_DECLARE_PARAMETER(FrameSize, int);
CONFIGURABLE_DECLARE_PARAMETER(Gain, int);
CONFIGURABLE_DECLARE_PARAMETER(PTSOffset, int64_t);
CONFIGURABLE_DECLARE_PARAMETER(SignalLost, bool);
};
}
}
#endif

239
video/core/AudioDecoder.cpp Normal file
View file

@ -0,0 +1,239 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioDecoder.h"
#include "Logger.h"
#include "Demuxer.h"
#include "System.h"
#include <Poco/Exception.h>
#include <Logger.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* 追帧(catch-up)阈值配置
* 说明:所有阈值均以"队列中待解码包个数"为单位。
* 音频帧典型时长约 20ms(AAC),队列上限 300,约 6s 缓冲。
*******************************************************************************
*/
namespace
{
constexpr int CATCH_UP_ENTER = 10;
constexpr int CATCH_UP_EXIT = 2;
constexpr int DROP_LEVEL_1 = 6;
constexpr int DROP_LEVEL_2 = 15;
constexpr int DROP_LEVEL_3 = 30;
}
/*
*******************************************************************************
* * CLASS DEFINE
*******************************************************************************
*/
AudioDecodeHandler::AudioDecodeHandler(const std::string& name, CodecID codecID, AudioComponent::Ptr pPrevious)
: _pPrevious(pPrevious)
{
}
AudioDecodeHandler::~AudioDecodeHandler()
{
}
void AudioDecodeHandler::conceal(Packet::Ptr pPacket)
{
(void)pPacket;
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioDecoder::AudioDecoder(const std::string& name)
: AbstractAudioInput(name), _synchronizer(name + "/sync"),
_catchUpEnabled(name.find("NET") != std::string::npos ? true : false), _isCatchingUp(false), _dropPhase(0)
{
}
AudioDecoder::~AudioDecoder()
{
}
void AudioDecoder::start()
{
run();
}
void AudioDecoder::stop()
{
cancel();
setTimeline(0);
_isCatchingUp = false;
_dropPhase = 0;
}
Frame::Ptr AudioDecoder::readFrame()
{
if (previousComponents().size() == 0 || packetQueues().empty())
throw Poco::NotFoundException("No previous components");
updateCatchUpState();
Frame::Ptr pFrame = nullptr;
if (!_pHandler.isNull())
{
pFrame = _pHandler->read();
if (!pFrame.isNull())
{
syncFrame(pFrame, _isCatchingUp);
return pFrame;
}
}
while (pFrame.isNull())
{
Packet::Ptr pPacket;
try
{
long timeOut = _isCatchingUp ? 1 : 1000;
pPacket = packetQueues().first()->dequeue(timeOut);
}
catch (std::exception& e)
{
if (packetQueues().first()->size() == 0)
{
if (_isCatchingUp)
{
_isCatchingUp = false;
_dropPhase = 0;
_synchronizer.reset();
logger().information("%s: catch-up exit (queue drained)", name());
}
}
setTimeline(0);
throw;
}
if (serialCodeWatcher().isChanged(pPacket->serialCode(), true))
{
auto pPrevious = previousComponents().first();
auto pDemuxer = pPrevious.cast<Demuxer>();
if (pDemuxer)
{
auto pEncoder = pDemuxer->audioEncoder();
pEncoder->config().copyTo(config());
serialCodeManager().increment();
_pHandler = AudioDecodeHandler::create(pEncoder->getCodecID(), pEncoder);
if (_catchUpEnabled)
{
while (packetQueues().first()->size() > 2)
{
try
{
packetQueues().first()->dequeue(0);
}
catch (...)
{
break;
}
}
}
}
else
throw Poco::NotImplementedException(
Poco::format("Unsupported previous component: %s", pPrevious->name()));
}
if (_isCatchingUp && shouldDropPacket())
{
_pHandler->conceal(pPacket);
continue;
}
_pHandler->write(pPacket);
pFrame = _pHandler->read();
}
syncFrame(pFrame, _isCatchingUp);
return pFrame;
}
void AudioDecoder::onLink(Core::Component::Ptr pPrevious)
{
if (!packetQueues().empty() && packetQueues().firstComponent() != pPrevious)
throw Poco::LogicException(name() + ": Support only one previous component, conflict with: " +
pPrevious->name() + ", Current: " + packetQueues().firstComponent()->name());
packetQueues().add(pPrevious, name(), MediaType::AUDIO);
}
void AudioDecoder::onUnlink(Core::Component::Ptr pPrevious)
{
packetQueues().remove(pPrevious);
}
void AudioDecoder::syncFrame(Frame::Ptr pFrame, bool isCatchingUp)
{
if (!isCatchingUp)
_synchronizer.syncFrame(pFrame->timestamp);
setTimeline(pFrame->timestamp);
pFrame->timestamp = System::timestamp();
}
void AudioDecoder::updateCatchUpState()
{
if (!_catchUpEnabled)
return;
const int queueSize = packetQueues().first()->size();
if (!_isCatchingUp && queueSize > CATCH_UP_ENTER)
{
_isCatchingUp = true;
_dropPhase = 0;
logger().information("%s: catch-up enter (queue=%d)", name(), queueSize);
}
else if (_isCatchingUp && queueSize <= CATCH_UP_EXIT)
{
_isCatchingUp = false;
_dropPhase = 0;
_synchronizer.reset();
logger().information("%s: catch-up exit (queue=%d)", name(), queueSize);
}
}
bool AudioDecoder::shouldDropPacket()
{
const int queueSize = packetQueues().first()->size();
bool drop = false;
if (queueSize >= DROP_LEVEL_3)
{
drop = (_dropPhase % 3) != 2;
}
else if (queueSize >= DROP_LEVEL_2)
{
drop = (_dropPhase % 2) == 0;
}
else
{
drop = (_dropPhase % 3) == 0;
}
_dropPhase++;
return drop;
}
}
}

119
video/core/AudioDecoder.h Normal file
View file

@ -0,0 +1,119 @@
#ifndef D60BD16B_B6AC_40EF_9E0E_D70A203EA49C
#define D60BD16B_B6AC_40EF_9E0E_D70A203EA49C
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AbstractAudioInput.h"
#include "util/FactoryManager.h"
#include "util/Task.h"
#include "System.h"
#include "FrameSynchronizer.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioDecodeHandlerSelector
{
public:
static CodecID select(CodecID codec, AudioComponent::Ptr)
{
return codec;
}
};
class AudioDecodeHandler: public ::Util::FactoryManager<AudioDecodeHandler, CodecID, AudioDecodeHandlerSelector,
CodecID, AudioComponent::Ptr>
{
public:
using Ptr = Poco::SharedPtr<AudioDecodeHandler>;
public:
AudioDecodeHandler(const std::string& name, CodecID codecID, AudioComponent::Ptr pPrevious);
virtual ~AudioDecodeHandler();
public:
const std::string& name() const;
AudioComponent::Ptr previous() const;
virtual void write(Packet::Ptr pPacket) = 0;
virtual Frame::Ptr read() = 0;
virtual void conceal(Packet::Ptr pPacket);
private:
const std::string _name;
AudioComponent::Ptr _pPrevious;
};
inline const std::string& AudioDecodeHandler::name() const
{
return _name;
}
inline AudioComponent::Ptr AudioDecodeHandler::previous() const
{
return _pPrevious;
}
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioDecoderSelector
{
public:
static int select(const std::string&)
{
return 0;
}
};
class AudioDecoder: public AbstractAudioInput,
public ::Util::FactoryManager<AudioDecoder, int, AudioDecoderSelector, const std::string&>
{
public:
using Ptr = Poco::AutoPtr<AudioDecoder>;
public:
AudioDecoder(const std::string& name);
virtual ~AudioDecoder();
protected:
void start() override;
void stop() override;
Frame::Ptr readFrame() override;
void onLink(Core::Component::Ptr pPrevious) override;
void onUnlink(Core::Component::Ptr pPrevious) override;
void syncFrame(Frame::Ptr pFrame, bool isCatchingUp = false);
PROPERTY_DECLARE(Timeline, uint64_t, 0, public, public)
private:
void updateCatchUpState();
bool shouldDropPacket();
AudioDecodeHandler::Ptr _pHandler;
FrameSynchronizer _synchronizer;
bool _catchUpEnabled;
bool _isCatchingUp;
uint32_t _dropPhase;
};
}
}
#endif

View file

@ -0,0 +1,85 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioEncoder.h"
#include "Logger.h"
#include <cstdint>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioEncoder::AudioEncoder(const std::string& name, CodecID codecID): AudioAdaptablePipe(name)
{
setCodecID(codecID);
setBitrate(128000);
}
AudioEncoder::~AudioEncoder()
{
}
void AudioEncoder::start()
{
_task.run(
[this](std::atomic<bool>& isCancelled) -> void
{
Packet::Ptr pPacket;
while (!isCancelled)
{
try
{
pPacket = encode();
if (!pPacket)
continue;
postToNextComponents(sharedFromThis(), pPacket, MediaType::AUDIO);
}
catch (Poco::Exception& e)
{
logger().error("%s: %s", name(), e.displayText());
Poco::Thread::sleep(1000);
}
catch (std::exception& e)
{
logger().error("%s: %s", name(), std::string(e.what()));
Poco::Thread::sleep(1000);
}
}
});
}
void AudioEncoder::stop()
{
_task.cancel();
}
Frame::Ptr AudioEncoder::readFrame(uint32_t timeout)
{
Frame::Ptr pFrame = AudioAdaptablePipe::readFrame(timeout);
const int64_t off = _ptsOffsetUs.load(std::memory_order_relaxed);
if (off != 0)
{
const int64_t ts = (int64_t)pFrame->timestamp + off;
pFrame->timestamp = (ts > 0) ? (uint64_t)ts : 0ULL;
}
return pFrame;
}
void AudioEncoder::onConfigChanged(ParameterNames& names)
{
if (names.exists(_PTSOffset))
_ptsOffsetUs.store(getPTSOffset(), std::memory_order_relaxed);
}
}
}

66
video/core/AudioEncoder.h Normal file
View file

@ -0,0 +1,66 @@
#ifndef E7CCDC55_4323_4B0A_A08E_1C11B939AA61
#define E7CCDC55_4323_4B0A_A08E_1C11B939AA61
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioAdaptablePipe.h"
#include "CodecID.h"
#include "util/FactoryManager.h"
#include "util/Task.h"
#include <Poco/SharedPtr.h>
#include <atomic>
#include <functional>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioEncoderSelector
{
public:
static CodecID select(CodecID codecID)
{
return codecID;
}
};
class AudioEncoder: public AudioAdaptablePipe,
public ::Util::FactoryManager<AudioEncoder, CodecID, AudioEncoderSelector, CodecID>
{
public:
using Ptr = Poco::AutoPtr<AudioEncoder>;
public:
AudioEncoder(const std::string& name, CodecID codecID);
virtual ~AudioEncoder();
public:
CONFIGURABLE_DECLARE_PARAMETER(CodecID, CodecID);
CONFIGURABLE_DECLARE_PARAMETER(Bitrate, int);
protected:
void start() override;
void stop() override;
Frame::Ptr readFrame(uint32_t timeout = 1000) override;
void onConfigChanged(ParameterNames& names) override;
virtual Packet::Ptr encode() = 0;
protected:
std::atomic<int64_t> _ptsOffsetUs{0};
private:
Util::Task _task;
AudioResample::Ptr _pResample;
AudioFrameAdapter::Ptr _pFrameAdapter;
};
}
}
#endif

View file

@ -0,0 +1,122 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioFrameAdapter.h"
#include "System.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioFrameAdapter::AudioFrameAdapter(ChannelLayout layout, SampleFormat format, int frameSize, int sampleRate)
{
int channels = channelLayoutChannels(layout);
_linesize = sampleFormatCalcLineSize(format, layout, frameSize);
_lines = sampleFormatIsPlanar(format) ? channels : 1;
_sampleDuration = 1000000000ul / sampleRate;
_sampleSize = sampleFormatBytesPerSample(format);
if (!sampleFormatIsPlanar(format))
_sampleSize *= channels;
_readIndex = 0;
_sampleRate = sampleRate;
_frameSize = frameSize;
_encodeClock = 0;
}
void AudioFrameAdapter::put(Frame::Ptr pFrame)
{
if (!_pInFrame.isNull())
throw Poco::InvalidAccessException();
_pInFrame = pFrame;
}
Frame::Ptr AudioFrameAdapter::get()
{
int inRemain = remainOfReadFrame();
if (inRemain == 0)
return nullptr;
if (_pOutFrame.isNull())
{
_pOutFrame = new Frame();
for (int i = 0; i < _lines; i++)
_pOutFrame->data[i] = new uint8_t[_linesize];
_pOutFrame->length = _linesize;
_writeIndex = 0;
}
if (0 == _pOutFrame->timestamp)
{
_pOutFrame->timestamp = getInFrameTimestamp();
const uint64_t wall = System::timestamp();
if (0 == _encodeClock)
_encodeClock = wall;
else
{
const int64_t diff = (int64_t)(wall - _encodeClock);
if (diff > 300000 || diff < -300000)
_encodeClock = wall;
else
_encodeClock += (uint64_t)(diff / 8);
}
_pOutFrame->timestamp = _encodeClock;
_encodeClock += 1000000ull * _frameSize / _sampleRate;
}
int bytes = std::min(inRemain, spaceOfOutFrame());
for (int i = 0; i < _lines; i++)
memcpy(_pOutFrame->data[i] + _writeIndex, _pInFrame->data[i] + _readIndex, bytes);
_writeIndex += bytes;
_readIndex += bytes;
if (remainOfReadFrame() == 0)
{
_pInFrame.reset();
_readIndex = 0;
}
Frame::Ptr pFrame = nullptr;
if (spaceOfOutFrame() == 0)
{
pFrame = _pOutFrame;
_pOutFrame.reset();
}
return pFrame;
}
int AudioFrameAdapter::remainOfReadFrame()
{
if (_pInFrame.isNull())
return 0;
return _pInFrame->length - _readIndex;
}
int AudioFrameAdapter::spaceOfOutFrame()
{
if (_pOutFrame.isNull())
return 0;
return _pOutFrame->length - _writeIndex;
}
uint64_t AudioFrameAdapter::getInFrameTimestamp()
{
return _pInFrame->timestamp + _readIndex / _sampleSize * _sampleDuration / 1000;
}
}
}

View file

@ -0,0 +1,58 @@
#ifndef B0BCC994_AC90_4B90_9CD2_10586E958794
#define B0BCC994_AC90_4B90_9CD2_10586E958794
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Frame.h"
#include "SampleFormat.h"
#include "ChannelLayout.h"
#include <Poco/SharedPtr.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioFrameAdapter
{
public:
using Ptr = Poco::SharedPtr<AudioFrameAdapter>;
public:
AudioFrameAdapter(ChannelLayout layout, SampleFormat format, int frameSize, int sampleRate);
public:
void put(Frame::Ptr pFrame);
Frame::Ptr get();
protected:
int remainOfReadFrame();
int spaceOfOutFrame();
uint64_t getInFrameTimestamp();
private:
int _linesize;
int _lines;
int _sampleSize;
Frame::Ptr _pInFrame;
Frame::Ptr _pOutFrame;
uint32_t _readIndex;
uint32_t _writeIndex;
uint32_t _sampleDuration;
int _sampleRate;
uint32_t _frameSize;
uint64_t _encodeClock;
};
}
}
#endif

244
video/core/AudioMixer.cpp Normal file
View file

@ -0,0 +1,244 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioMixer.h"
#include "System.h"
#include <cstdint>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioMixer::Channel::Channel(AudioMixer::Ptr pMixer, uint32_t id)
: AudioAdaptablePipe(Poco::format(pMixer->name() + "/CH#%u", id)), _pMixer(pMixer), _id(id)
{
setAttach(false);
setInput(nullptr);
}
AudioMixer::Channel::~Channel()
{
}
void AudioMixer::Channel::start()
{
}
void AudioMixer::Channel::stop()
{
}
void AudioMixer::Channel::onConfigChanged(ParameterNames& names)
{
if (names.exists(_Input))
unlink(true, false);
if (names.exists(_Attach))
{
auto pInput = getInput();
if (pInput)
{
if (getAttach())
{
getInput()->link(sharedFromThis());
link(mixer());
}
else
getInput()->unlink(sharedFromThis());
}
}
serialCodeWatcher().reset();
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioMixer::AudioMixer(const std::string& name): AbstractAudioInput(name)
{
_mixFrame = new Frame();
for (int i = 0; i < Frame::DATA_LINES; i++)
_mixFrame->data[i] = new uint8_t[1024 * 32];
}
AudioMixer::~AudioMixer()
{
}
AudioMixer::Channel::Ptr AudioMixer::getChannel(uint32_t id)
{
if (_channels.find(id) == _channels.end())
{
Channel::Ptr pChannel = new Channel(Ptr(this, true), id);
syncConfigToChannel(pChannel);
_channels[id] = pChannel;
}
return _channels[id];
}
void AudioMixer::syncConfigToChannel(AudioMixer::Channel::Ptr pChannel)
{
pChannel->setSampleRate(getSampleRate());
pChannel->setChannelLayout(getChannelLayout());
pChannel->setSampleFormat(getSampleFormat());
pChannel->setFrameSize(getFrameSize());
}
template <typename T, typename S>
static inline void mix(const S MIN, const S MAX, uint8_t* i0, uint8_t* i1, uint8_t* o, uint32_t bytes)
{
T* p0 = reinterpret_cast<T*>(i0);
T* p1 = reinterpret_cast<T*>(i1);
T* output = reinterpret_cast<T*>(o);
const size_t count = static_cast<size_t>(bytes / sizeof(T));
for (size_t i = 0; i < count; ++i)
{
S a = static_cast<S>(p0[i]);
S b = static_cast<S>(p1[i]);
const int64_t product = static_cast<int64_t>(a) * b;
S c = a + b - static_cast<S>(product >> 0x10);
c = (c > MAX) ? MAX : ((c < MIN) ? MIN : c);
output[i] = static_cast<T>(c);
}
}
void AudioMixer::start()
{
refreshOutputParams();
run();
}
void AudioMixer::stop()
{
cancel();
}
void AudioMixer::onConfigChanged(ParameterNames& names)
{
for (auto& m : _channels)
{
auto pChannel = m.second;
syncConfigToChannel(pChannel);
pChannel->commit();
}
AbstractAudioInput::onConfigChanged(names);
}
Frame::Ptr AudioMixer::readFrame()
{
Frame::Ptr pFrame = nullptr;
uint64_t pts = 0;
uint32_t index = 0;
for (auto& m : _channels)
{
auto pChannel = m.second;
Frame::Ptr pReadFrame;
try
{
pReadFrame = pChannel->readFrame(32);
}
catch (...)
{
pReadFrame = nullptr;
}
if (!pReadFrame.isNull())
{
if (pts == 0 || pts > pReadFrame->timestamp)
pts = pReadFrame->timestamp;
if (pFrame == nullptr)
pFrame = pReadFrame;
else
{
#define AUDIO_DATA(i) index > 0 ? _mixFrame->data[i].get() : pFrame->data[i].get()
switch (getSampleFormat())
{
case SampleFormat::U8:
case SampleFormat::U8P:
for (uint32_t i = 0; i < getChannels(); i++)
mix<uint8_t, uint16_t>(0, UINT8_MAX, AUDIO_DATA(i), pReadFrame->data[i].get(),
_mixFrame->data[i].get(), pFrame->length);
break;
case SampleFormat::S16:
case SampleFormat::S16P:
for (uint32_t i = 0; i < getChannels(); i++)
mix<int16_t, int32_t>(INT16_MIN, INT16_MAX, AUDIO_DATA(i), pReadFrame->data[i].get(),
_mixFrame->data[i].get(), pFrame->length);
break;
case SampleFormat::S32:
case SampleFormat::S32P:
for (uint32_t i = 0; i < getChannels(); i++)
mix<int32_t, int64_t>(INT32_MIN, INT32_MAX, AUDIO_DATA(i), pReadFrame->data[i].get(),
_mixFrame->data[i].get(), pFrame->length);
break;
case SampleFormat::S64:
case SampleFormat::S64P:
for (uint32_t i = 0; i < getChannels(); i++)
mix<int64_t, int64_t>(INT64_MIN, INT64_MAX, AUDIO_DATA(i), pReadFrame->data[i].get(),
_mixFrame->data[i].get(), pFrame->length);
break;
case SampleFormat::FLT:
case SampleFormat::FLTP:
for (uint32_t i = 0; i < getChannels(); i++)
mix<float, float>(-1.0f, 1.0f, AUDIO_DATA(i), pReadFrame->data[i].get(),
_mixFrame->data[i].get(), pFrame->length);
break;
case SampleFormat::DBL:
case SampleFormat::DBLP:
for (uint32_t i = 0; i < getChannels(); i++)
mix<double, double>(-1.0, 1.0, AUDIO_DATA(i), pReadFrame->data[i].get(),
_mixFrame->data[i].get(), pFrame->length);
break;
default:
break;
}
index++;
}
}
}
if (pFrame.isNull())
return nullptr;
_traffic++;
if (index > 0)
{
_mixFrame->timestamp = pts;
_mixFrame->length = pFrame->length;
_mixFrame->code = pFrame->code;
return _mixFrame;
}
else
return pFrame;
}
void AudioMixer::refreshOutputParams()
{
setFrameDuration((float)1000000 / getSampleRate() * getFrameSize());
setChannels(channelLayoutChannels(getChannelLayout()));
setLineSize(sampleFormatCalcLineSize(getSampleFormat(), getChannelLayout(), getFrameSize()));
}
}
}

102
video/core/AudioMixer.h Normal file
View file

@ -0,0 +1,102 @@
#ifndef AC195091_8442_4268_B843_700AB594416A
#define AC195091_8442_4268_B843_700AB594416A
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioAdaptablePipe.h"
#include "AbstractAudioInput.h"
#include "util/FactoryManager.h"
#include <unordered_map>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioMixerSelector
{
public:
static int select(const std::string&)
{
return 0;
}
};
class AudioMixer: public AbstractAudioInput,
public ::Util::FactoryManager<AudioMixer, int, AudioMixerSelector, const std::string&>
{
public:
using Ptr = Poco::AutoPtr<AudioMixer>;
class Channel: public AudioAdaptablePipe
{
public:
using Ptr = Poco::AutoPtr<Channel>;
protected:
Channel(AudioMixer::Ptr, uint32_t id);
public:
~Channel();
CONFIGURABLE_DECLARE_PARAMETER(Attach, bool)
CONFIGURABLE_DECLARE_PARAMETER(Input, AudioComponent::Ptr)
uint32_t id() const;
protected:
virtual void start() override;
virtual void stop() override;
void onConfigChanged(ParameterNames& names) override;
AudioMixer::Ptr mixer();
private:
AudioMixer::Ptr _pMixer;
uint32_t _id;
friend AudioMixer;
};
public:
AudioMixer(const std::string& name);
~AudioMixer();
Channel::Ptr getChannel(uint32_t id);
void syncConfigToChannel(Channel::Ptr pChannel);
protected:
void start() override;
void stop() override;
void onConfigChanged(ParameterNames& names) override;
Frame::Ptr readFrame() override;
void refreshOutputParams();
PROPERTY_DECLARE(FrameDuration, uint32_t, 0, protected, protected)
PROPERTY_DECLARE(Channels, uint32_t, 0, protected, protected)
PROPERTY_DECLARE(LineSize, uint32_t, 0, protected, protected)
private:
std::unordered_map<uint32_t, Channel::Ptr> _channels;
Frame::Ptr _mixFrame;
};
inline uint32_t AudioMixer::Channel::id() const
{
return _id;
}
inline AudioMixer::Ptr AudioMixer::Channel::mixer()
{
return _pMixer;
}
}
}
#endif

View file

@ -0,0 +1,58 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioOutput.h"
#include "Logger.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
void AudioOutput::start()
{
_task.run(
[this](std::atomic<bool>& isCancelled) -> void
{
while (!isCancelled)
{
try
{
Frame::Ptr pFrame = readFrame(1000);
writeFrame(pFrame);
}
catch (Poco::Exception& e)
{
logger().error("%s: %s", name(), e.displayText());
Poco::Thread::sleep(1000);
}
catch (std::exception& e)
{
logger().error("%s: %s", name(), std::string(e.what()));
Poco::Thread::sleep(1000);
}
}
});
}
void AudioOutput::stop()
{
_task.cancel();
}
void AudioOutput::onLink(Core::Component::Ptr pPrevious)
{
if (!frameQueues().empty() && frameQueues().firstComponent() != pPrevious)
throw Poco::LogicException(name() + ": Support only one previous component, conflict with: " +
pPrevious->name() + ", Current: " + frameQueues().firstComponent()->name());
frameQueues().add(pPrevious, name(), MediaType::AUDIO, 6);
}
}
}

52
video/core/AudioOutput.h Normal file
View file

@ -0,0 +1,52 @@
#ifndef F16FAA29_322A_4A89_945E_E56FFADAC2CD
#define F16FAA29_322A_4A89_945E_E56FFADAC2CD
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioAdaptablePipe.h"
#include "util/FactoryManager.h"
#include "util/Task.h"
#include <string>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioOutputSelector
{
public:
static const std::string select(const std::string& arg)
{
auto pos = arg.find(':');
return (pos != std::string::npos) ? arg.substr(0, pos) : arg;
}
};
class AudioOutput
: public AudioAdaptablePipe,
public ::Util::FactoryManager<AudioOutput, const std::string, AudioOutputSelector, const std::string&>
{
public:
using AudioAdaptablePipe::AudioAdaptablePipe;
protected:
void start() override;
void stop() override;
virtual void writeFrame(Frame::Ptr pFrame) = 0;
void onLink(Core::Component::Ptr pPrevious) override;
private:
Util::Task _task;
};
}
}
#endif

View file

@ -0,0 +1,28 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioResample.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioResample::AudioResample(const std::string& name, Core::ChannelLayout ilayout, Core::SampleFormat iformat,
int irate, Core::ChannelLayout olayout, Core::SampleFormat oformat, int orate)
: _name(name), _ilayout(ilayout), _iformat(iformat), _irate(irate), _olayout(olayout), _oformat(oformat),
_orate(orate)
{
}
AudioResample::~AudioResample()
{
}
}
}

100
video/core/AudioResample.h Normal file
View file

@ -0,0 +1,100 @@
#ifndef F62FEA52_888A_474C_B58C_B83007D75F89
#define F62FEA52_888A_474C_B58C_B83007D75F89
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "ChannelLayout.h"
#include "SampleFormat.h"
#include "Frame.h"
#include "util/FactoryManager.h"
#include <Poco/SharedPtr.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioResampleSelector
{
public:
static int select(ChannelLayout, SampleFormat, int, ChannelLayout, SampleFormat, int)
{
return 0;
}
};
class AudioResample: public ::Util::FactoryManager<AudioResample, int, AudioResampleSelector, ChannelLayout,
SampleFormat, int, ChannelLayout, SampleFormat, int>
{
public:
using Ptr = Poco::SharedPtr<AudioResample>;
public:
AudioResample(const std::string& name, Core::ChannelLayout ilayout, Core::SampleFormat iformat, int irate,
Core::ChannelLayout olayout, Core::SampleFormat oformat, int orate);
virtual ~AudioResample();
public:
virtual Frame::Ptr process(Frame::Ptr in) = 0;
const std::string name() const;
const Core::ChannelLayout inLayout() const;
const Core::SampleFormat inFormat() const;
const int inRate() const;
const Core::ChannelLayout outLayout() const;
const Core::SampleFormat outFormat() const;
const int outRate() const;
private:
const std::string _name;
Core::ChannelLayout _ilayout;
Core::SampleFormat _iformat;
int _irate;
Core::ChannelLayout _olayout;
Core::SampleFormat _oformat;
int _orate;
};
inline const std::string AudioResample::name() const
{
return _name;
}
inline const Core::ChannelLayout AudioResample::inLayout() const
{
return _ilayout;
}
inline const Core::SampleFormat AudioResample::inFormat() const
{
return _iformat;
}
inline const int AudioResample::inRate() const
{
return _irate;
}
inline const Core::ChannelLayout AudioResample::outLayout() const
{
return _olayout;
}
inline const Core::SampleFormat AudioResample::outFormat() const
{
return _oformat;
}
inline const int AudioResample::outRate() const
{
return _orate;
}
}
}
#endif

View file

@ -0,0 +1,37 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "BitrateControl.h"
#include <Poco/Format.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* LOCAL VARIABLES
*******************************************************************************
*/
static const std::string _bcStrings[] = {
"CBR", "VBR", "AVBR", "QVBR", "CVBR", "FIXQP", "QPMAP",
};
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
const std::string bitrateControlToString(BitrateControl bc)
{
unsigned index = (unsigned)bc;
if (index >= (unsigned)BitrateControl::NB)
return Poco::format("UNKNOWN(%u)", index);
return _bcStrings[index];
}
}
}

View file

@ -0,0 +1,40 @@
#ifndef BE785709_C62A_4233_A050_C24D88194CE4
#define BE785709_C62A_4233_A050_C24D88194CE4
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include <string>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
enum class BitrateControl
{
CBR,
VBR,
AVBR,
QVBR,
CVBR,
FIXQP,
QPMAP,
NB,
};
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
const std::string bitrateControlToString(BitrateControl bc);
}
}
#endif

View file

@ -0,0 +1,79 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "ChannelLayout.h"
#include <Poco/Format.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* TYPEDEFS
*******************************************************************************
*/
struct ChannelLayoutInfo
{
std::string name;
int channels;
};
/*
*******************************************************************************
* LOCAL VARIABLES
*******************************************************************************
*/
static const ChannelLayoutInfo channelLayoutInfo[(int)ChannelLayout::NB] = {
{
"L",
1,
},
{
"R",
1,
},
{
"MONO",
1,
},
{
"STEREO",
2,
},
};
/*
*******************************************************************************
* LOCAL FUNCTIONS
*******************************************************************************
*/
static inline bool isChannelLayoutValid(ChannelLayout layout)
{
return ((int)layout >= 0) && (layout < ChannelLayout::NB);
}
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
int channelLayoutChannels(ChannelLayout layout)
{
if (isChannelLayoutValid(layout))
return channelLayoutInfo[(int)layout].channels;
return 0;
}
const std::string channelLayoutToString(const ChannelLayout layout)
{
if (isChannelLayoutValid(layout))
return channelLayoutInfo[(int)layout].name;
return Poco::format("UNKNOWN(%u)", (uint32_t)layout);
}
}
}

View file

@ -0,0 +1,34 @@
#ifndef CD9FD626_A6CF_41FF_B2B2_050F7BEA7195
#define CD9FD626_A6CF_41FF_B2B2_050F7BEA7195
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include <string>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
enum class ChannelLayout
{
L,
R,
MONO,
STEREO,
NB,
};
int channelLayoutChannels(ChannelLayout layout);
const std::string channelLayoutToString(const ChannelLayout layout);
}
}
#endif

View file

@ -0,0 +1,61 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "CodecCapabilityManager.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
const int CodecCapabilityManager::INVALID_HANDLE = -1;
CodecCapabilityManager::CodecCapabilityManager(uint64_t total): _total(total), _handles(1024)
{
}
CodecCapabilityManager::Handle CodecCapabilityManager::alloc(uint32_t width, uint32_t height, uint32_t frameRate)
{
uint64_t v = width * height * frameRate;
Poco::FastMutex::ScopedLock lock(_lock);
if (v <= _total)
{
try
{
Handle h = _handles.allocHandle();
_allocRecord[h] = v;
_total -= v;
return h;
}
catch (...)
{
}
}
return INVALID_HANDLE;
}
void CodecCapabilityManager::free(Handle handle)
{
if (handle != INVALID_HANDLE)
{
Poco::FastMutex::ScopedLock lock(_lock);
try
{
_handles.freeHandle(handle);
_total += _allocRecord[handle];
_allocRecord.erase(handle);
}
catch (...)
{
}
}
}
}
}

View file

@ -0,0 +1,42 @@
#ifndef B88D46AC_14A3_4C6F_ADE1_2AF8BB03F51A
#define B88D46AC_14A3_4C6F_ADE1_2AF8BB03F51A
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "util/HandlesManager.h"
#include <Poco/Mutex.h>
#include <map>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class CodecCapabilityManager
{
public:
using Handle = int;
static const int INVALID_HANDLE;
public:
CodecCapabilityManager(uint64_t total);
public:
Handle alloc(uint32_t width, uint32_t height, uint32_t frameRate);
void free(Handle handle);
private:
uint64_t _total;
Util::HandlesManager<Handle> _handles;
std::map<Handle, uint64_t> _allocRecord;
Poco::FastMutex _lock;
};
}
}
#endif

39
video/core/CodecID.cpp Normal file
View file

@ -0,0 +1,39 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "CodecID.h"
#include <Poco/Format.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* LOCAL VARIABLES
*******************************************************************************
*/
static const std::string _strings[] = {
"H264", "H265", "MJPEG",
"MP3", "AAC", "OPUS", "G711A", "G711U",
};
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
const std::string codecIDToString(const CodecID id)
{
unsigned index = (unsigned)id;
if (index >= (unsigned)CodecID::NB)
return Poco::format("UNKNOWN(%u)", index);
return _strings[index];
}
}
}

42
video/core/CodecID.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef BB532961_7DA1_415B_A98F_EB80C5C9DC17
#define BB532961_7DA1_415B_A98F_EB80C5C9DC17
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include <string>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
enum class CodecID
{
H264,
H265,
MJPEG,
MP3,
AAC,
OPUS,
G711A,
G711U,
NB,
};
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
const std::string codecIDToString(const CodecID codecID);
}
}
#endif

35
video/core/CodecProfile.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef D1AC8D55_FFF4_4CDD_9A78_3DD77F9417B4
#define D1AC8D55_FFF4_4CDD_9A78_3DD77F9417B4
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
enum class CodecProfile
{
H264_BASELINE,
H264_MAIN,
H264_HIGH,
H264_EXTENDED,
H264_SVC_T,
H264_SVC_Q,
H264_SVC_S,
H265_MAIN,
H265_MAIN_10,
NB,
};
}
}
#endif

82
video/core/Color.cpp Executable file
View file

@ -0,0 +1,82 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Color.h"
#include "Poco/NumberParser.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Color::Color(std::string s)
{
uint32_t value = 0xffffff;
try
{
int pos = s.find('#');
if (pos >= 0)
value = Poco::NumberParser::parseHex(s.substr(pos + 1));
else
value = Poco::NumberParser::parse(s);
}
catch (...)
{
}
new (this) Color(value);
}
Color::Color(uint32_t n)
{
new (this) Color((n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff);
}
Color::Color()
{
new (this) Color(0);
}
void Color::toARGB1555Pixel(uint8_t v[2])
{
uint32_t alpha = (a + 127) / 255;
uint32_t red = (r * 31 + 127) / 255;
uint32_t green = (g * 31 + 127) / 255;
uint32_t blue = (b * 31 + 127) / 255;
uint16_t pixel = (alpha << 15) | (red << 10) | (green << 5) | blue;
v[0] = pixel;
v[1] = pixel >> 8;
}
void Color::toARGB4444Pixel(uint8_t v[2])
{
uint16_t pixel = (a >> 4) << 12 | (r >> 4) << 8 | (g >> 4) << 4 | (b >> 4);
v[0] = pixel;
v[1] = pixel >> 8;
}
void Color::toRGB888Pixel(uint8_t v[3])
{
v[0] = r;
v[1] = g;
v[2] = b;
}
void Color::toARGB8888Pixel(uint8_t v[4])
{
v[0] = b;
v[1] = g;
v[2] = r;
v[3] = a;
}
}
}

47
video/core/Color.h Executable file
View file

@ -0,0 +1,47 @@
#ifndef D9CEFFBC_A4C8_4C8D_9D61_5A2E533AFA42
#define D9CEFFBC_A4C8_4C8D_9D61_5A2E533AFA42
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include <stdint.h>
#include <string>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
struct Color
{
Color(uint8_t a, uint8_t r, uint8_t g, uint8_t b): a(a), r(r), g(g), b(b)
{
}
Color(uint8_t r, uint8_t g, uint8_t b): Color(255, r, g, b)
{
}
Color(std::string s);
Color(uint32_t n);
Color();
void toARGB1555Pixel(uint8_t v[2]);
void toARGB4444Pixel(uint8_t v[2]);
void toRGB888Pixel(uint8_t v[3]);
void toARGB8888Pixel(uint8_t v[4]);
uint8_t a;
uint8_t r;
uint8_t g;
uint8_t b;
};
}
}
#endif

350
video/core/Component.cpp Normal file
View file

@ -0,0 +1,350 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Component.h"
#include "System.h"
#include "util/Math.h"
#include "Logger.h"
#include <unistd.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
bool Component::Collection::add(Ptr pObject)
{
Poco::FastMutex::ScopedLock lock(_lock);
auto iter = std::find(_list.begin(), _list.end(), pObject);
if (iter == _list.end())
{
_list.push_back(pObject);
return true;
}
return false;
}
bool Component::Collection::remove(Ptr pObject)
{
Poco::FastMutex::ScopedLock lock(_lock);
auto iter = std::find(_list.begin(), _list.end(), pObject);
if (iter != _list.end())
{
_list.remove(pObject);
return true;
}
return false;
}
void Component::Collection::foreach (std::function<void(Ptr)> func)
{
std::vector<Ptr> v;
{
Poco::FastMutex::ScopedLock lock(_lock);
for (auto& ptr : _list)
v.push_back(ptr);
}
for (auto& ptr : v)
func(ptr);
}
uint32_t Component::Collection::size()
{
Poco::FastMutex::ScopedLock lock(_lock);
return _list.size();
}
Component::Ptr Component::Collection::first()
{
return *(_list.begin());
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Component::Component(const std::string& name): _name(name), _frameQueues(this), _packetQueues(this), _started(false)
{
logger().debug("%s(%#x): CREATE.", name, reinterpret_cast<uintptr_t>(this));
}
Component::~Component()
{
logger().debug("%s(%#x): RELEASE.", name(), reinterpret_cast<uintptr_t>(this));
}
Component::Ptr Component::link(Ptr pNext)
{
if (pNext.isNull())
return nullptr;
if (nextComponents().add(pNext))
{
logger().debug("%s(%#x): ===> %s(%#x)", name(), reinterpret_cast<uintptr_t>(this), pNext->name(),
reinterpret_cast<uintptr_t>(pNext.get()));
pNext->doLink(sharedFromThis());
}
return pNext;
}
void Component::unlink(Component::Ptr pNext)
{
if (pNext.isNull())
return;
if (nextComponents().remove(pNext))
{
logger().debug("%s(%#x): =X=> %s(%#x)", name(), reinterpret_cast<uintptr_t>(this), pNext->name(),
reinterpret_cast<uintptr_t>(pNext.get()));
pNext->doUnlink(sharedFromThis());
}
}
void Component::unlink(bool unlinkAllPrevious, bool unlinkAllNext)
{
if (unlinkAllPrevious)
{
previousComponents().foreach ([this](Ptr pObject) -> void { pObject->unlink(sharedFromThis()); });
}
if (unlinkAllNext)
{
nextComponents().foreach ([this](Ptr pObject) -> void { unlink(pObject); });
}
}
void Component::enable(const void* pUser, const std::string& desc)
{
logger().debug("%s(%#x): Enabled by %s(%#x)", name(), reinterpret_cast<uintptr_t>(this), desc,
reinterpret_cast<uintptr_t>(pUser));
Poco::Mutex::ScopedLock lock(_componentsLock);
bool shouldStart = (userCount() == 0);
incrementUserRefCount(pUser);
if (shouldStart)
{
frameQueues().flush();
packetQueues().flush();
initialize();
previousComponents().foreach ([&](Ptr ptr) -> void { ptr->enable(this); });
logger().debug("%s(%#x): START.", name(), reinterpret_cast<uintptr_t>(this));
start();
_traffic = 0;
_started = true;
}
}
void Component::disable(const void* pUser, const std::string& desc, bool ignoreRefCount)
{
logger().debug("%s(%#x): Disabled by %s(%#x)", name(), reinterpret_cast<uintptr_t>(this), desc,
reinterpret_cast<uintptr_t>(pUser));
Poco::Mutex::ScopedLock lock(_componentsLock);
if (!started())
{
logger().debug("%s(%#x): Stop already.", name(), reinterpret_cast<uintptr_t>(this));
return;
}
if (ignoreRefCount)
eraseUser(pUser);
else
decrementUserRefCount(pUser);
if (userCount() == 0)
{
logger().debug("%s(%#x): STOP.", name(), reinterpret_cast<uintptr_t>(this));
stop();
deinitialize();
_traffic = 0;
_started = false;
previousComponents().foreach ([&](Component::Ptr ptr) -> void { ptr->disable(this); });
frameQueues().flush();
packetQueues().flush();
}
}
int Component::userCount()
{
Poco::Mutex::ScopedLock lock(_componentsLock);
return _users.size();
}
int Component::nextComponentCount()
{
return nextComponents().size();
}
int Component::previousComponentCount()
{
return previousComponents().size();
}
QueueStatus Component::queueStatus(Ptr who)
{
{
auto pQueue = packetQueues().find(who);
if (pQueue)
return pQueue->status();
}
{
auto pQueue = frameQueues().find(who);
if (pQueue)
return pQueue->status();
}
return QueueStatus::UNKNOWN;
}
void Component::initialize()
{
}
void Component::deinitialize()
{
}
void Component::doLink(Ptr pPrevious)
{
if (previousComponents().add(pPrevious))
{
onLink(pPrevious);
if (started())
pPrevious->enable(this);
}
}
void Component::onLink(Ptr pPrevious)
{
}
void Component::doUnlink(Ptr pPrevious)
{
if (previousComponents().remove(pPrevious))
{
onUnlink(pPrevious);
pPrevious->disable(this, true);
}
}
void Component::onUnlink(Ptr pPrevious)
{
}
void Component::post(Ptr pSender, Frame::Ptr pFrame, MediaType type, bool force)
{
post(pSender, frameQueues(), pFrame, type, force);
}
void Component::post(Ptr pSender, Packet::Ptr pPacket, MediaType type, bool force)
{
post(pSender, packetQueues(), pPacket->clone(), type, force);
}
void Component::notify(Notification::Ptr pNotification, bool shouldPropagate)
{
_notificationCenter.postNotification(pNotification);
if (shouldPropagate)
{
logger().debug("%s(Sender is '%s'): Notify '%s' to next components", name(), pNotification->getSender()->name(),
pNotification->name());
nextComponents().foreach (
[&](Component::Ptr ptr) -> void
{
if (ptr->started())
ptr->notify(pNotification, shouldPropagate);
});
}
}
void Component::notifyNextComponents(Ptr pSender, Notification::Ptr pNotification, bool shouldPropagate)
{
logger().debug("%s: Notify '%s' to next components", pSender->name(), pNotification->name());
pNotification->setSender(pSender);
nextComponents().foreach (
[&](Component::Ptr ptr) -> void
{
if (ptr->started())
ptr->notify(pNotification, shouldPropagate);
});
}
void Component::incrementUserRefCount(const void* pUser)
{
if (_users.find(pUser) == _users.end())
_users[pUser] = 0;
_users[pUser]++;
}
void Component::decrementUserRefCount(const void* pUser)
{
if (_users.find(pUser) != _users.end())
{
_users[pUser]--;
if (_users[pUser] <= 0)
_users.erase(pUser);
}
}
void Component::eraseUser(const void* pUser)
{
_users.erase(pUser);
}
bool Component::syncWait(uint64_t ts, std::atomic<bool>& isCancelled)
{
uint64_t now = System::timestamp();
while (ts > now && !isCancelled)
{
now = System::timestamp();
usleep(100);
}
return (now >= ts);
}
bool Component::syncWait(uint64_t ts, uint32_t maxWaitTime)
{
const uint64_t startTime = System::timestamp();
const uint64_t timeout = maxWaitTime * 1000;
while (true)
{
uint64_t now = System::timestamp();
if (now >= ts)
return true;
if (now - startTime >= timeout)
return false;
const uint64_t remaining = ::Util::Math::min(ts - now, timeout - (now - startTime));
const useconds_t us = ::Util::Math::clamp(remaining / 10, 1000ULL, 100000ULL);
usleep(us);
}
}
void Component::publishEvent(Event event, bool sync)
{
if (sync)
EventOccurred.notify(this, event);
else
EventOccurred.notifyAsync(this, event);
}
}
}

408
video/core/Component.h Normal file
View file

@ -0,0 +1,408 @@
#ifndef AB01F7C9_DADD_4ADA_88F6_ED371CF36813
#define AB01F7C9_DADD_4ADA_88F6_ED371CF36813
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "util/Configurable.h"
#include "util/ClassAssistant.h"
#include "Frame.h"
#include "Packet.h"
#include <Poco/AutoPtr.h>
#include <Poco/Mutex.h>
#include <Poco/NotificationCenter.h>
#include <Poco/NObserver.h>
#include <Poco/RefCountedObject.h>
#include <Poco/SharedPtr.h>
#include <Poco/BasicEvent.h>
#include <list>
#include <unordered_map>
#include <functional>
#include <type_traits>
#include <unistd.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Component: public Poco::RefCountedObject, public Util::Configurable
{
public:
enum class Event
{
IMAGE_SIZE_CHANGED,
};
using Ptr = Poco::AutoPtr<Component>;
class Collection
{
public:
bool add(Ptr);
bool remove(Ptr);
void foreach (std::function<void(Ptr)> func);
uint32_t size();
Ptr first();
private:
std::list<Ptr> _list;
Poco::FastMutex _lock;
};
class Notification: public Poco::Notification
{
public:
using Ptr = Poco::AutoPtr<Notification>;
public:
PROPERTY_DECLARE(Sender, Component::Ptr, nullptr, public, public);
std::string name() const override
{
return Util::ClassAssistant::demangle(*this);
}
};
template <class QT> class Queues
{
public:
Queues(Ptr pOwner);
void add(Ptr, const std::string& name, MediaType, int limit = 300);
void remove(Ptr);
void clear();
bool exists(Ptr);
bool empty();
typename QT::Ptr first();
typename QT::Ptr find(Ptr);
Ptr firstComponent();
void flush();
template <typename F> void foreach (const F&& func);
private:
Ptr _pOwner;
Poco::FastMutex _mutex;
std::map<Ptr, typename QT::Ptr> _queues;
};
public:
Component(const std::string& name);
Component(const Component&) = delete;
virtual ~Component();
public:
const std::string& name() const;
Ptr link(Ptr pNext);
void unlink(Ptr pNext);
void unlink(bool unlinkAllPrevious, bool unlinkAllNext);
template <class T> void enable(T* pObject);
template <class T> void disable(T* pObject, bool ignoreRefCount = false);
template <class T> bool enabled(T* pObject);
Ptr sharedFromThis();
void enable(const void* pUser, const std::string& desc);
void disable(const void* pUser, const std::string& desc, bool ignoreRefCount = false);
int userCount();
int nextComponentCount();
int previousComponentCount();
const SerialCode& code();
QueueStatus queueStatus(Ptr who);
virtual uint32_t traffic();
bool started() const;
Poco::BasicEvent<Event> EventOccurred;
protected:
virtual void start() = 0;
virtual void stop() = 0;
virtual void initialize();
virtual void deinitialize();
virtual void doLink(Ptr pPrevious);
virtual void onLink(Ptr pPrevious);
virtual void doUnlink(Ptr pPrevious);
virtual void onUnlink(Ptr pPrevious);
Collection& previousComponents();
Collection& nextComponents();
SerialCodeManager& serialCodeManager();
SerialCodeWatcher& serialCodeWatcher();
void post(Ptr pSender, Frame::Ptr pFrame, MediaType type, bool force);
void post(Ptr pSender, Packet::Ptr pPacket, MediaType type, bool force);
template <class TQueue, class TObjectPtr>
void post(Ptr pSender, TQueue& queues, TObjectPtr pObject, MediaType type, bool force);
template <class TObjectPtr> void postToNextComponents(Ptr pSender, TObjectPtr pObject, MediaType type);
template <class TObjectPtr>
bool postToNextComponents(Ptr pSender, TObjectPtr pObject, MediaType type, std::atomic<bool>& isCancelled);
template <class N, class C, class H> void registerNotificationHandle(C& object, const H& handle);
template <class N, class C, class H> void unregisterNotificationHandle(C& object, const H& handle);
void notify(Notification::Ptr pNotification, bool shouldPropagate);
void notifyNextComponents(Ptr pSender, Notification::Ptr pNotification, bool shouldPropagate = false);
Queues<FrameQueue>& frameQueues();
Queues<PacketQueue>& packetQueues();
void incrementUserRefCount(const void* pUser);
void decrementUserRefCount(const void* pUser);
void eraseUser(const void* pUser);
bool syncWait(uint64_t ts, std::atomic<bool>& isCancelled);
bool syncWait(uint64_t ts, uint32_t maxWaitTime);
void publishEvent(Event event, bool sync = true);
protected:
uint32_t _traffic;
private:
const std::string _name;
Collection _previousComponents;
Collection _nextComponents;
Poco::Mutex _componentsLock;
Queues<FrameQueue> _frameQueues;
Queues<PacketQueue> _packetQueues;
std::unordered_map<const void*, int> _users;
bool _started;
SerialCodeManager _serialCodeManager;
SerialCodeWatcher _serialCodeWatcher;
Poco::NotificationCenter _notificationCenter;
};
template <class QT> inline Component::Queues<QT>::Queues(Ptr pOwner): _pOwner(pOwner)
{
}
template <class QT> void Component::Queues<QT>::add(Ptr pComponent, const std::string& name, MediaType type, int limit)
{
if (exists(pComponent))
throw Poco::ExistsException(_pOwner->name() + "'s queue: " + pComponent->name() + " EXISTS!");
Poco::FastMutex::ScopedLock lock(_mutex);
_queues[pComponent] = new QT(name, type, limit);
}
template <class QT> void Component::Queues<QT>::remove(Ptr pComponent)
{
Poco::FastMutex::ScopedLock lock(_mutex);
_queues.erase(pComponent);
}
template <class QT> inline void Component::Queues<QT>::clear()
{
Poco::FastMutex::ScopedLock lock(_mutex);
return _queues.clear();
}
template <class QT> inline bool Component::Queues<QT>::exists(Ptr pComponent)
{
Poco::FastMutex::ScopedLock lock(_mutex);
return _queues.find(pComponent) != _queues.end();
}
template <class QT> inline bool Component::Queues<QT>::empty()
{
Poco::FastMutex::ScopedLock lock(_mutex);
return _queues.empty();
}
template <class QT> inline typename QT::Ptr Component::Queues<QT>::first()
{
Poco::FastMutex::ScopedLock lock(_mutex);
if (_queues.empty())
return nullptr;
return _queues.begin()->second;
}
template <class QT> inline typename QT::Ptr Component::Queues<QT>::find(Ptr pComponent)
{
Poco::FastMutex::ScopedLock lock(_mutex);
const auto it = _queues.find(pComponent);
if (it != _queues.end())
return it->second;
return nullptr;
}
template <class QT> inline Component::Ptr Component::Queues<QT>::firstComponent()
{
Poco::FastMutex::ScopedLock lock(_mutex);
if (_queues.empty())
return nullptr;
return _queues.begin()->first;
}
template <class QT> inline void Component::Queues<QT>::flush()
{
Poco::FastMutex::ScopedLock lock(_mutex);
for (auto& q : _queues)
q.second->clear();
}
template <class QT> template <typename F> inline void Component::Queues<QT>::foreach (const F&& func)
{
Poco::FastMutex::ScopedLock lock(_mutex);
for (auto& q : _queues)
func(q.first, q.second);
}
inline const std::string& Component::name() const
{
return _name;
}
inline bool Component::started() const
{
return _started;
}
inline const SerialCode& Component::code()
{
return serialCodeManager().code();
}
inline uint32_t Component::traffic()
{
return _traffic;
}
inline Component::Collection& Component::previousComponents()
{
return _previousComponents;
}
inline Component::Collection& Component::nextComponents()
{
return _nextComponents;
}
inline SerialCodeManager& Component::serialCodeManager()
{
return _serialCodeManager;
}
inline SerialCodeWatcher& Component::serialCodeWatcher()
{
return _serialCodeWatcher;
}
inline Component::Queues<FrameQueue>& Component::frameQueues()
{
return _frameQueues;
}
inline Component::Queues<PacketQueue>& Component::packetQueues()
{
return _packetQueues;
}
template <class T> inline void Component::enable(T* pObject)
{
enable(pObject, std::is_base_of<Component, T>::value ? reinterpret_cast<Component*>(pObject)->name()
: Util::ClassAssistant::demangle<T>());
}
template <class T> inline void Component::disable(T* pObject, bool ignoreRefCount)
{
disable(pObject,
std::is_base_of<Component, T>::value ? reinterpret_cast<Component*>(pObject)->name()
: Util::ClassAssistant::demangle<T>(),
ignoreRefCount);
}
template <class T> inline bool Video::Core::Component::enabled(T* pUser)
{
Poco::Mutex::ScopedLock lock(_componentsLock);
return (_users.find(pUser) != _users.end());
}
template <class TQueue, class TObjectPtr>
inline void Component::post(Ptr pSender, TQueue& queues, TObjectPtr pObject, MediaType type, bool force)
{
auto pQueue = queues.find(pSender);
if (pQueue && pQueue->type() == type)
{
pQueue->enqueue(pObject, force);
_traffic++;
}
}
template <class TObjectPtr> inline void Component::postToNextComponents(Ptr pSender, TObjectPtr pObject, MediaType type)
{
nextComponents().foreach (
[&](Ptr ptr) -> void
{
if (ptr->started())
ptr->post(pSender, pObject, type, true);
});
}
template <class TObjectPtr>
inline bool Component::postToNextComponents(Ptr pSender, TObjectPtr pObject, MediaType type,
std::atomic<bool>& isCancelled)
{
int count = 0;
int num = 0;
nextComponents().foreach (
[&](Ptr ptr) -> void
{
if (ptr->started())
{
num++;
do
{
try
{
ptr->post(pSender, pObject, type, false);
count++;
break;
}
catch (...)
{
if (!isCancelled)
::usleep(1000);
}
} while (!isCancelled);
}
});
return count == num;
}
template <class N, class C, class H> inline void Component::registerNotificationHandle(C& object, const H& handle)
{
_notificationCenter.addObserver(Poco::NObserver<C, N>(object, handle));
}
template <class N, class C, class H> inline void Component::unregisterNotificationHandle(C& object, const H& handle)
{
_notificationCenter.removeObserver(Poco::NObserver<C, N>(object, handle));
}
inline Component::Ptr Component::sharedFromThis()
{
return Ptr(this, true);
}
}
}
#endif

312
video/core/Demuxer.cpp Normal file
View file

@ -0,0 +1,312 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Demuxer.h"
#include "System.h"
#include "Logger.h"
#include "util/Broadcast.h"
#include <Poco/Format.h>
#include <unistd.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
class DummyVideoEncoder: public VideoEncoder
{
public:
DummyVideoEncoder(const std::string& name): VideoEncoder(name, CodecID::H264)
{
}
protected:
void onConfigChanged(ParameterNames& names) override
{
if (names.exists(_CodecID))
serialCodeManager().put(_CodecID, getCodecID());
if (names.exists(_Width))
serialCodeManager().put(_Width, getWidth());
if (names.exists(_Height))
serialCodeManager().put(_Height, getHeight());
if (names.exists(_FrameRate))
serialCodeManager().put(_FrameRate, getFrameRate());
}
};
class DummyAudioEncoder: public AudioEncoder
{
public:
DummyAudioEncoder(const std::string& name): AudioEncoder(name, CodecID::AAC)
{
}
protected:
Core::Packet::Ptr encode() override
{
return nullptr;
}
void onConfigChanged(ParameterNames& names) override
{
if (names.exists(_CodecID))
serialCodeManager().put(_CodecID, getCodecID());
if (names.exists(_SampleRate))
serialCodeManager().put(_SampleRate, getSampleRate());
if (names.exists(_SampleFormat))
serialCodeManager().put(_SampleFormat, getSampleFormat());
}
};
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
DemuxHandler::DemuxHandler(const std::string& name, const std::string& uri)
: _name(name), _uri(uri), _scheme(Poco::URI(uri).getScheme()), _timeDrift(0)
{
}
DemuxHandler::~DemuxHandler()
{
}
int64_t DemuxHandler::getTimeDrift(uint64_t ts)
{
if (0 == _timeDrift)
_timeDrift = getBaseline() - ts;
return _timeDrift;
}
void DemuxHandler::resetTimeDrift()
{
_timeDrift = 0;
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
class DemuxerHandlerController: public Util::BroadcastReceiver, public Util::BroadcastReceiverRegistry
{
public:
DemuxerHandlerController(DemuxHandler::Ptr pHandler, int index, std::atomic<bool>& isCancelled)
: _skip(false), _newIndex(0), _newPosition(0), _pHandler(pHandler), _index(index), _isCancelled(isCancelled)
{
Util::IntentFilter* pFilter = new Util::IntentFilter;
pFilter->addAction("carousel.position");
registerReceiver(*this, pFilter);
}
~DemuxerHandlerController()
{
unregisterReceiver(*this);
}
protected:
void onReceive(Util::Intent::Ptr pIntent) override
{
int index = pIntent->getExtra<int>("index");
int position = pIntent->getExtra<int>("position");
if (index < 0 || position < 0)
return;
uint64_t ts = position * 1000000u;
if (index != _index)
{
_skip = true;
_newIndex = index;
_newPosition = ts;
_isCancelled = true;
return;
}
_pHandler->seek(ts);
}
READONLY_PROPERTY_DECLARE(skip, bool)
READONLY_PROPERTY_DECLARE(newIndex, int)
READONLY_PROPERTY_DECLARE(newPosition, uint64_t)
private:
DemuxHandler::Ptr _pHandler;
int _index;
std::atomic<bool>& _isCancelled;
};
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Demuxer::Demuxer(const std::string& name)
: Component(name), _pVideoEncoder(new DummyVideoEncoder(name + "/VideoEncoder")),
_pAudioEncoder(new DummyAudioEncoder(name + "/AudioEncoder"))
{
setUsingTCPTransport(false);
setURICollection({});
}
Demuxer::~Demuxer()
{
}
#define __try_run(x) \
try \
{ \
x \
} \
catch (Poco::Exception & e) \
{ \
logger().error("%s: %s", name(), e.displayText()); \
Poco::Thread::sleep(2000); \
} \
catch (std::exception & e) \
{ \
logger().error("%s: %s", name(), std::string(e.what())); \
Poco::Thread::sleep(2000); \
}
void Demuxer::start()
{
if (getURICollection().size() == 0)
{
logger().warning("%s: URI collection is empty!", name());
return;
}
_task.run(
[this](std::atomic<bool>& isCancelled) -> void
{
_playIndex = 0;
PlayIndexChanged.notify(this);
int index = 0;
uint64_t position = 0;
uint64_t timeline = 1000000;
while (!isCancelled)
{
std::string uri;
DemuxHandler::Ptr pHandler;
__try_run({ uri = getURICollection().at(index); });
__try_run({ pHandler = DemuxHandler::create(uri); });
if (!pHandler.isNull())
{
__try_run({
_playPosition = 0;
if (Poco::URI(uri).getScheme() == "rtsp")
pHandler->setUsingTCPTransport(getUsingTCPTransport());
pHandler->setBaseline(timeline);
Poco::SharedPtr<DemuxerHandlerController> pController;
if (Poco::URI(uri).getScheme() == "file")
{
pController = new DemuxerHandlerController(pHandler, index, isCancelled);
if (position)
{
pHandler->seek(position);
position = 0;
}
}
pHandler->run(
videoEncoder(), audioEncoder(),
[&](Packet::Ptr pPacket, MediaType type) -> void
{
if (postToNextComponents(sharedFromThis(), pPacket, type, isCancelled))
{
int64_t pos = pHandler->getPosition();
int64_t diff = std::abs(pos - _playPosition);
if (diff >= 1000000)
{
_playPosition = pos;
PlayPositionChanged.notify(this);
}
}
},
isCancelled);
timeline = pHandler->getTimeline();
if (pController && pController->skip())
{
isCancelled = false;
index = pController->newIndex();
position = pController->newPosition();
_playIndex = index;
_playPosition = position;
PlayIndexChanged.notify(this);
PlayPositionChanged.notify(this);
continue;
}
});
}
else
logger().error("%s: Not implemented URI scheme '%s'", name(), Poco::URI(uri).getScheme());
if (++index >= (int)getURICollection().size())
index = 0;
if (_playIndex != index)
{
_playIndex = index;
PlayIndexChanged.notify(this);
}
}
});
}
void Demuxer::stop()
{
_task.cancel();
}
void Demuxer::onLink(Component::Ptr pPrevious)
{
throw Poco::LogicException("Not supported for link");
}
void Demuxer::onUnlink(Component::Ptr pPrevious)
{
throw Poco::LogicException("Not supported for link");
}
void Demuxer::onConfigChanged(ParameterNames& names)
{
if (!started())
return;
if (names.exists(_UsingTCPTransport) || names.exists(_URICollection))
{
stop();
start();
}
}
}
}

149
video/core/Demuxer.h Normal file
View file

@ -0,0 +1,149 @@
#ifndef ADECE10E_B193_46F7_B377_651677F4AD89
#define ADECE10E_B193_46F7_B377_651677F4AD89
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Component.h"
#include "util/FactoryManager.h"
#include "util/SimpleFactory.h"
#include "VideoEncoder.h"
#include "AudioEncoder.h"
#include "util/Task.h"
#include "util/ClassAssistant.h"
#include <Poco/BasicEvent.h>
#include <Poco/SharedPtr.h>
#include <Poco/URI.h>
#include <vector>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class DemuxHandlerSelector
{
public:
static const std::string select(const std::string& uri)
{
return Poco::URI(uri).getScheme();
}
};
class DemuxHandler
: public ::Util::FactoryManager<DemuxHandler, const std::string, DemuxHandlerSelector, const std::string&>
{
public:
using Ptr = Poco::SharedPtr<DemuxHandler>;
using Post = std::function<void(Packet::Ptr, MediaType)>;
using ShouldWait = std::function<bool(void)>;
public:
DemuxHandler(const std::string& name, const std::string& uri);
virtual ~DemuxHandler();
public:
virtual void run(VideoEncoder::Ptr pVideoEncoder, AudioEncoder::Ptr pAudioEncoder, const Post& post,
std::atomic<bool>& isCancelled) = 0;
const std::string& name() const;
const std::string& uri() const;
const std::string& scheme() const;
virtual void seek(uint64_t ts) = 0;
PROPERTY_DECLARE(UsingTCPTransport, bool, false, public, public)
PROPERTY_DECLARE(Baseline, uint64_t, 0, public, public)
PROPERTY_DECLARE(Timeline, uint64_t, 0, public, public)
PROPERTY_DECLARE(Position, int64_t, 0, protected, public)
protected:
int64_t getTimeDrift(uint64_t ts);
void resetTimeDrift();
private:
const std::string _name;
const std::string _uri;
const std::string _scheme;
int64_t _timeDrift;
};
inline const std::string& DemuxHandler::name() const
{
return _name;
}
inline const std::string& DemuxHandler::uri() const
{
return _uri;
}
inline const std::string& DemuxHandler::scheme() const
{
return _scheme;
}
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Demuxer: public Component, public ::Util::SimpleFactory<Demuxer, const std::string&>
{
public:
using Ptr = Poco::AutoPtr<Demuxer>;
using URICollection = std::vector<std::string>;
public:
Demuxer(const std::string& name);
~Demuxer();
public:
CONFIGURABLE_DECLARE_PARAMETER(UsingTCPTransport, bool)
CONFIGURABLE_DECLARE_PARAMETER(URICollection, URICollection&)
Poco::BasicEvent<void> PlayIndexChanged;
Poco::BasicEvent<void> PlayPositionChanged;
const int playIndex() const;
VideoEncoder::Ptr videoEncoder();
AudioEncoder::Ptr audioEncoder();
protected:
void start() override;
void stop() override;
void onLink(Component::Ptr pPrevious) override;
void onUnlink(Component::Ptr pPrevious) override;
void onConfigChanged(ParameterNames& names) override;
READONLY_PROPERTY_DECLARE(playIndex, int)
READONLY_PROPERTY_DECLARE(playPosition, int64_t)
private:
Util::Task _task;
VideoEncoder::Ptr _pVideoEncoder;
AudioEncoder::Ptr _pAudioEncoder;
};
inline const int Demuxer::playIndex() const
{
return _playIndex;
}
inline VideoEncoder::Ptr Demuxer::videoEncoder()
{
return _pVideoEncoder;
}
inline AudioEncoder::Ptr Demuxer::audioEncoder()
{
return _pAudioEncoder;
}
}
}
#endif

19
video/core/Frame.cpp Normal file
View file

@ -0,0 +1,19 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Frame.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
const int Frame::DATA_LINES = NUM_DATA_POINTERS;
}
}

42
video/core/Frame.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef D8F82C97_DBD1_40D1_9ABD_E78F9B225CF5
#define D8F82C97_DBD1_40D1_9ABD_E78F9B225CF5
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "NotificationQueue.h"
#include "SerialCode.h"
#include <Poco/SharedPtr.h>
/*
*******************************************************************************
* CONSTANTS
*******************************************************************************
*/
#define NUM_DATA_POINTERS 8
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
struct Frame
{
using Ptr = Poco::SharedPtr<Frame>;
static const int DATA_LINES;
Poco::SharedPtr<uint8_t> data[NUM_DATA_POINTERS];
uint32_t length;
uint64_t timestamp;
SerialCode code;
};
using FrameQueue = NotificationQueue<Frame>;
}
}
#endif

View file

@ -0,0 +1,111 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "FrameSynchronizer.h"
#include "util/Math.h"
#include <thread>
#include <iostream>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
FrameSynchronizer::DriftPredictor::DriftPredictor(size_t windowSize): _windowSize(windowSize), _lastPrediction(0)
{
}
void FrameSynchronizer::DriftPredictor::update(int64_t drift)
{
_samples.push_back(drift);
if (_samples.size() > _windowSize)
_samples.pop_front();
constexpr double alpha = 0.2;
_lastPrediction = alpha * drift + (1 - alpha) * _lastPrediction;
}
int64_t FrameSynchronizer::DriftPredictor::predict()
{
if (_samples.empty())
return _lastPrediction;
if (_samples.size() < 5)
return _lastPrediction;
double sum = 0;
for (auto v : _samples)
sum += v;
double mean = sum / _samples.size();
constexpr int64_t MAX_DRIFT = 30000;
int64_t result = static_cast<int64_t>(0.7 * mean + 0.3 * _lastPrediction);
return Util::Math::clamp(result, -MAX_DRIFT, MAX_DRIFT);
}
void FrameSynchronizer::DriftPredictor::reset()
{
_samples.clear();
_lastPrediction = 0;
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
FrameSynchronizer::FrameSynchronizer(const std::string& name): _name(name), _baseClock(0), _lastSysTS(0)
{
_predictor.reset();
}
FrameSynchronizer::~FrameSynchronizer()
{
}
void FrameSynchronizer::reset()
{
_baseClock = 0;
_lastSysTS = 0;
_predictor.reset();
}
void FrameSynchronizer::syncFrame(uint64_t frameTS)
{
const uint64_t now = System::timestamp();
if (_baseClock == 0)
{
_baseClock = now - frameTS;
_lastSysTS = now;
return;
}
uint64_t ts = frameTS + _baseClock;
if (ts + 500000 < _lastSysTS || ts > _lastSysTS + 10000000)
{
_predictor.reset();
_baseClock = now - frameTS;
_lastSysTS = now;
return;
}
const uint64_t targetTime = frameTS + _baseClock + _predictor.predict();
int64_t waitTime = static_cast<int64_t>(targetTime) - static_cast<int64_t>(now);
if (waitTime > 0)
std::this_thread::sleep_for(std::chrono::microseconds(waitTime));
const uint64_t actualTime = System::timestamp();
_predictor.update(static_cast<int64_t>(actualTime - targetTime));
_lastSysTS = actualTime;
}
}
}

View file

@ -0,0 +1,58 @@
#ifndef B6D2EB85_A2D9_4037_BDBB_5ADCDA3E3B01
#define B6D2EB85_A2D9_4037_BDBB_5ADCDA3E3B01
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "System.h"
#include "util/ClassAssistant.h"
#include <atomic>
#include <cmath>
#include <chrono>
#include <mutex>
#include <deque>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class FrameSynchronizer
{
public:
FrameSynchronizer(const std::string& name);
~FrameSynchronizer();
void syncFrame(uint64_t frameTs);
void reset();
private:
class DriftPredictor
{
public:
DriftPredictor(size_t windowSize = 10);
void update(int64_t drift);
int64_t predict();
void reset();
private:
std::deque<int64_t> _samples;
size_t _windowSize;
double _lastPrediction;
};
READONLY_PROPERTY_DECLARE(name, std::string)
uint64_t _baseClock;
uint64_t _lastSysTS;
DriftPredictor _predictor;
};
}
}
#endif

54
video/core/Frontend.cpp Normal file
View file

@ -0,0 +1,54 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Frontend.h"
#include "Logger.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Frontend::Frontend(std::string name): _name(name)
{
}
Frontend::~Frontend()
{
}
std::string Frontend::name()
{
return _name;
}
void Frontend::notifyStatusChanged()
{
MediaInfo info;
read(info);
notifyStatusChanged(info);
}
void Frontend::notifyStatusChanged(MediaInfo& info)
{
try
{
StatusChanged.notify(this, info);
}
catch (Poco::Exception& e)
{
logger().error("%s: status changed: %s", name(), e.displayText());
}
catch (std::exception& e)
{
logger().error("%s: status changed: %s", name(), std::string(e.what()));
}
}
}
}

63
video/core/Frontend.h Normal file
View file

@ -0,0 +1,63 @@
#ifndef F4486E13_26A4_47A9_B4C0_193369C1A700
#define F4486E13_26A4_47A9_B4C0_193369C1A700
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoScanMode.h"
#include <Poco/BasicEvent.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Frontend
{
public:
using Ptr = Poco::SharedPtr<Frontend>;
struct MediaInfo
{
bool haveVideo;
bool haveAudio;
struct
{
uint32_t width;
uint32_t height;
uint32_t frameRate;
VideoScanMode scanMode;
} video;
struct
{
uint32_t sampleRate;
} audio;
};
public:
Poco::BasicEvent<MediaInfo> StatusChanged;
public:
Frontend(std::string name);
virtual ~Frontend();
std::string name();
virtual void read(MediaInfo& info) = 0;
protected:
void notifyStatusChanged();
void notifyStatusChanged(MediaInfo& info);
private:
std::string _name;
};
}
}
#endif

40
video/core/GOPMode.cpp Normal file
View file

@ -0,0 +1,40 @@
#ifndef BB0BDE8C_97B1_4665_97FE_505C703706CD
#define BB0BDE8C_97B1_4665_97FE_505C703706CD
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "GOPMode.h"
#include <Poco/Format.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* LOCAL VARIABLES
*******************************************************************************
*/
static const std::string _gmString[] = {
"NORMAL_P", "DUAL_P", "SMART_P", "ADV_SMART_P", "BIPRED_B", "LOWDELAY_B",
};
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
const std::string gopModeToString(GOPMode gm)
{
unsigned index = (unsigned)gm;
if (index >= (unsigned)GOPMode::NB)
return Poco::format("UNKNOWN(%u)", index);
return _gmString[index];
}
}
}
#endif

39
video/core/GOPMode.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef E0A1BF3F_980A_41BE_A542_2066821A64EB
#define E0A1BF3F_980A_41BE_A542_2066821A64EB
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include <string>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
enum class GOPMode
{
NORMAL_P,
DUAL_P,
SMART_P,
ADV_SMART_P,
BIPRED_B,
LOWDELAY_B,
NB
};
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
const std::string gopModeToString(GOPMode gm);
}
}
#endif

View file

@ -0,0 +1,27 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "GeometryTypes.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
bool Rect::operator!=(const Rect& other)
{
return x != other.x || y != other.y || width != other.width || height != other.height;
}
bool Size::operator!=(const Size& other)
{
return width != other.width || height != other.height;
}
}
}

View file

@ -0,0 +1,37 @@
#ifndef B9536093_A419_43A3_9220_7B1A65A2B31F
#define B9536093_A419_43A3_9220_7B1A65A2B31F
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
struct Rect
{
bool operator!=(const Rect& other);
int x;
int y;
int width;
int height;
};
struct Size
{
bool operator!=(const Size& other);
int width;
int height;
};
}
}
#endif

279
video/core/Image.cpp Normal file
View file

@ -0,0 +1,279 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Image.h"
#include "Logger.h"
#include "util/Math.h"
#include <freetype/ftglyph.h>
#include <freetype/fterrors.h>
#include <Poco/Exception.h>
#include <Poco/Format.h>
#include <Poco/UnicodeConverter.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
void Image::drawText(const std::string& text, const std::string& font, int size, Color foreColor, Color bgColor)
{
get_shared_channel(0).fill(bgColor.r);
get_shared_channel(1).fill(bgColor.g);
get_shared_channel(2).fill(bgColor.b);
get_shared_channel(3).fill(bgColor.a);
int x = 0, y = 0;
y = height() - y;
std::wstring wtext;
convertText(text, wtext);
runOnFreeType(font, size,
[&](FT_Face& face) -> void
{
FT_BBox bbox;
computeStringBBox(wtext, face, bbox);
FT_Vector pen;
pen.x = (x - bbox.xMin) * 64;
pen.y = (y - bbox.yMax) * 64;
for (size_t i = 0; i < wtext.length(); i++)
{
FT_Set_Transform(face, 0, &pen);
FT_UInt glyph_index = FT_Get_Char_Index(face, wtext.at(i));
int error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
if (error)
{
throw Poco::InvalidAccessException("字形加载失败", error);
}
error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
if (error)
{
throw Poco::InvalidAccessException("字形渲染失败", error);
}
int offsetX = face->glyph->bitmap_left;
int offsetY = height() - face->glyph->bitmap_top;
for (size_t y = 0; y < face->glyph->bitmap.rows; y++)
{
for (size_t x = 0; x < face->glyph->bitmap.width; x++)
{
uint8_t c = face->glyph->bitmap.buffer[y * face->glyph->bitmap.width + x];
if (c < 50)
continue;
(*this)(x + offsetX, y + offsetY, 0) = foreColor.r * c / 255;
(*this)(x + offsetX, y + offsetY, 1) = foreColor.g * c / 255;
(*this)(x + offsetX, y + offsetY, 2) = foreColor.b * c / 255;
(*this)(x + offsetX, y + offsetY, 3) = foreColor.a * c / 255;
}
}
pen.x += face->glyph->advance.x;
pen.y += face->glyph->advance.y;
}
});
}
void Image::drawRectangle(const Rect& rect, Color& color, int border)
{
if (border <= 0)
{
get_shared_channel(0).fill(color.r);
get_shared_channel(1).fill(color.g);
get_shared_channel(2).fill(color.b);
get_shared_channel(3).fill(color.a);
}
else
{
get_shared_channel(3).fill(0);
uint8_t c[4] = {color.r, color.g, color.b, color.a};
draw_rectangle(rect.x, rect.y, rect.x + rect.width, rect.y + border, c);
draw_rectangle(rect.x, rect.y + border, rect.x + border, rect.y + rect.height, c);
draw_rectangle(rect.x + rect.width - border, rect.y + border, rect.x + rect.width, rect.y + rect.height, c);
draw_rectangle(rect.x + border, rect.y + rect.height - border, rect.x + rect.width - border,
rect.y + rect.height, c);
}
}
void Image::drawImage(int x, int y, Ptr pImage)
{
const int width = pImage->width();
const int height = pImage->height();
for (int sy = 0; sy < height; ++sy)
{
for (int sx = 0; sx < width; ++sx)
{
const int dx = x + sx;
const int dy = y + sy;
if (dx >= 0 && dx < this->width() && dy >= 0 && dy < this->height())
{
Color color;
pImage->getPixel(sx, sy, color);
(*this)(dx, dy, 0, 0) = color.r;
(*this)(dx, dy, 0, 1) = color.g;
(*this)(dx, dy, 0, 2) = color.b;
(*this)(dx, dy, 0, 3) = color.a;
}
}
}
}
void Image::getPixel(int x, int y, Color& color)
{
uint8_t originAlpha = 0xff;
if (spectrum() == 1)
{
color.r = (*this)(x, y, 0, 0);
color.g = color.r;
color.b = color.r;
}
else if (spectrum() == 2)
{
color.r = (*this)(x, y, 0, 0);
color.g = (*this)(x, y, 0, 1);
color.b = 0;
}
else if (spectrum() == 3)
{
color.r = (*this)(x, y, 0, 0);
color.g = (*this)(x, y, 0, 1);
color.b = (*this)(x, y, 0, 2);
}
else if (spectrum() == 4)
{
color.r = (*this)(x, y, 0, 0);
color.g = (*this)(x, y, 0, 1);
color.b = (*this)(x, y, 0, 2);
originAlpha = (*this)(x, y, 0, 3);
}
if (_forceAlphaEnabled && originAlpha > _forceAlphaValue)
color.a = _forceAlphaValue;
else
color.a = originAlpha;
}
void Image::forceAlpha(uint8_t alpha)
{
_forceAlphaEnabled = true;
_forceAlphaValue = alpha;
}
void Image::computeStringSize(const std::string& text, const std::string& font, int fontsz, Size& size)
{
runOnFreeType(font, fontsz,
[&](FT_Face& face) -> void
{
FT_BBox bbox;
std::wstring wstr;
convertText(text, wstr);
computeStringBBox(wstr, face, bbox);
size.width = bbox.xMax - bbox.xMin;
size.height = bbox.yMax - bbox.yMin;
size.width = Util::Math::alignup(size.width, 2);
size.height = Util::Math::alignup(size.height, 2);
});
}
int Image::computeStringBBox(std::wstring text, FT_Face face, FT_BBox& bbox)
{
size_t i;
int error;
FT_BBox glyph_bbox;
FT_Vector pen;
FT_Glyph glyph;
FT_GlyphSlot slot = face->glyph;
bbox.xMin = bbox.yMin = 32000;
bbox.xMax = bbox.yMax = -32000;
pen.x = 0;
pen.y = 0;
for (i = 0; i < text.length(); i++)
{
FT_Set_Transform(face, 0, &pen);
error = FT_Load_Char(face, text.at(i), FT_LOAD_RENDER);
if (error)
{
logger().error("FT_Load_Char error: %d(%s)", error, std::string(FT_Error_String(error)));
return -1;
}
error = FT_Get_Glyph(face->glyph, &glyph);
if (error)
{
logger().error("FT_Get_Glyph error: %d(%s)", error, std::string(FT_Error_String(error)));
return -1;
}
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);
if (glyph_bbox.xMin < bbox.xMin)
bbox.xMin = glyph_bbox.xMin;
if (glyph_bbox.yMin < bbox.yMin)
bbox.yMin = glyph_bbox.yMin;
if (glyph_bbox.xMax > bbox.xMax)
bbox.xMax = glyph_bbox.xMax;
if (glyph_bbox.yMax > bbox.yMax)
bbox.yMax = glyph_bbox.yMax;
FT_Done_Glyph(glyph);
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
return 0;
}
void Image::convertText(const std::string& from, std::wstring& to)
{
Poco::UnicodeConverter::toUTF16(from, to);
}
void Image::runOnFreeType(const std::string& font, int size, std::function<void(FT_Face&)> func)
{
FT_Library library;
FT_Face face;
FT_Error error;
error = FT_Init_FreeType(&library);
if (error)
throw Poco::InvalidAccessException(
Poco::format("FreeType init error: %d(%s)", error, std::string(FT_Error_String(error))), error);
error = FT_New_Face(library, font.c_str(), 0, &face);
if (error)
throw Poco::InvalidAccessException(
Poco::format("FreeType new face error: %d(%s)", error, std::string(FT_Error_String(error))), error);
FT_Set_Pixel_Sizes(face, 0, size);
func(face);
FT_Done_Face(face);
FT_Done_FreeType(library);
}
}
}

52
video/core/Image.h Normal file
View file

@ -0,0 +1,52 @@
#ifndef CF80473A_40DD_43E5_B0D7_73F2205E9DF6
#define CF80473A_40DD_43E5_B0D7_73F2205E9DF6
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Color.h"
#include "GeometryTypes.h"
#include <CImg.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <Poco/SharedPtr.h>
#include <functional>
using namespace cimg_library;
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Image: public CImg<uint8_t>
{
public:
using Ptr = Poco::SharedPtr<Image>;
using CImg::CImg;
void drawText(const std::string& text, const std::string& font, int size, Color foreColor, Color bgColor);
void drawRectangle(const Rect& rect, Color& color, int border = 0);
void drawImage(int x, int y, Ptr pImage);
void getPixel(int x, int y, Color& color);
void forceAlpha(uint8_t alpha);
static void computeStringSize(const std::string& text, const std::string& font, int fontsz, Size& size);
protected:
static int computeStringBBox(std::wstring text, FT_Face face, FT_BBox& bbox);
static void convertText(const std::string& from, std::wstring& to);
static void runOnFreeType(const std::string& font, int size, std::function<void(FT_Face&)> func);
private:
bool _forceAlphaEnabled = false;
uint8_t _forceAlphaValue;
};
}
}
#endif

28
video/core/MediaType.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef D3D5F4A2_6250_4C2B_A293_7D6F3A83F95F
#define D3D5F4A2_6250_4C2B_A293_7D6F3A83F95F
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
enum class MediaType
{
UNKNOWN = -1,
VIDEO,
AUDIO,
DATA,
SUBTITLE,
ATTACHMENT,
NB
};
}
}
#endif

95
video/core/Muxer.cpp Normal file
View file

@ -0,0 +1,95 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Muxer.h"
#include "AudioComponent.h"
#include "VideoComponent.h"
#include "Logger.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Muxer::Muxer(const std::string& name, const std::string& url): Component(name), _url(url)
{
}
Muxer::~Muxer()
{
}
void Muxer::start()
{
_task.run(
[this](std::atomic<bool>& isCancelled) -> void
{
while (!isCancelled)
{
try
{
connect();
while (!isCancelled)
mux();
}
catch (Poco::Exception& e)
{
logger().error("%s(CONNECT/MUX): %s", name(), e.displayText());
}
catch (std::exception& e)
{
logger().error("%s(CONNECT/MUX): %s", name(), std::string(e.what()));
}
try
{
disconnect();
}
catch (Poco::Exception& e)
{
logger().error("%s(DISCONNECT): %s", name(), e.displayText());
}
catch (std::exception& e)
{
logger().error("%s(DISCONNECT): %s", name(), std::string(e.what()));
}
if (!isCancelled)
Poco::Thread::sleep(1000);
}
});
}
void Muxer::stop()
{
_task.cancel();
}
void Muxer::onLink(Component::Ptr pPrevious)
{
if (dynamic_cast<AudioComponent*>(pPrevious.get()))
packetQueues().add(pPrevious, name(), MediaType::AUDIO);
else if (dynamic_cast<VideoComponent*>(pPrevious.get()))
packetQueues().add(pPrevious, name(), MediaType::VIDEO);
else
{
logger().error("%s: Unsupport class '%s'", name(), std::string(typeid(*pPrevious).name()));
}
}
void Muxer::onUnlink(Component::Ptr pPrevious)
{
packetQueues().remove(pPrevious);
}
void Muxer::onConfigChanged(ParameterNames& names)
{
}
}
}

72
video/core/Muxer.h Normal file
View file

@ -0,0 +1,72 @@
#ifndef B61EDCD6_1C1C_4355_AD2C_4A09C55099D0
#define B61EDCD6_1C1C_4355_AD2C_4A09C55099D0
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Component.h"
#include "util/FactoryManager.h"
#include "util/Task.h"
#include <Poco/URI.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class MuxerSelector
{
public:
static const std::string select(const std::string& url)
{
return Poco::URI(url).getScheme();
}
};
class Muxer: public Component,
public ::Util::FactoryManager<Muxer, const std::string, MuxerSelector, const std::string&>
{
public:
using Ptr = Poco::AutoPtr<Muxer>;
public:
Muxer(const std::string& name, const std::string& url);
virtual ~Muxer();
public:
const std::string& url() const;
std::string scheme() const;
protected:
void start() override;
void stop() override;
void onLink(Component::Ptr pPrevious) override;
void onUnlink(Component::Ptr pPrevious) override;
void onConfigChanged(ParameterNames& names) override;
virtual void connect() = 0;
virtual void mux() = 0;
virtual void disconnect() = 0;
private:
std::string _url;
Util::Task _task;
};
inline const std::string& Muxer::url() const
{
return _url;
}
inline std::string Muxer::scheme() const
{
return Poco::URI(url()).getScheme();
}
}
}
#endif

View file

@ -0,0 +1,18 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "NotificationQueue.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
}
}

View file

@ -0,0 +1,132 @@
#ifndef D93EB1DD_2BCB_47B7_9361_91841576013D
#define D93EB1DD_2BCB_47B7_9361_91841576013D
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "MediaType.h"
#include <Poco/Notification.h>
#include <Poco/NotificationQueue.h>
#include <Poco/SharedPtr.h>
#include <vector>
#include <iostream>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
template <class T> class Notification: public Poco::Notification
{
public:
Notification(typename T::Ptr pObject): _pObject(pObject)
{
}
public:
typename T::Ptr get()
{
return _pObject;
}
private:
typename T::Ptr _pObject;
};
enum class QueueStatus
{
UNKNOWN = -1,
NORMAL,
OVERHALF,
FULL,
};
template <class T> class NotificationQueue
{
public:
using Ptr = Poco::SharedPtr<NotificationQueue<T>>;
public:
NotificationQueue(const std::string& name, MediaType type, int limit = 300);
public:
MediaType type() const;
void enqueue(typename T::Ptr pObject, bool force = true);
typename T::Ptr dequeue(long timeout);
void clear();
QueueStatus status();
int size();
private:
Poco::NotificationQueue _queue;
std::string _name;
MediaType _type;
int _limit;
};
template <class T>
inline NotificationQueue<T>::NotificationQueue(const std::string& name, MediaType type, int limit)
: _name(name), _type(type), _limit(limit)
{
}
template <class T> inline MediaType NotificationQueue<T>::type() const
{
return _type;
}
template <class T> inline void NotificationQueue<T>::enqueue(typename T::Ptr pObject, bool force)
{
if (_queue.size() >= _limit)
{
if (!force)
throw std::overflow_error("Queue overflow!");
std::cout << "NotificationQueue(" + _name + "[" << (type() == MediaType::VIDEO ? "Video" : "Audio")
<< "]): OVERFLOW!" << std::endl;
_queue.dequeueNotification()->release();
}
_queue.enqueueNotification(new Notification<T>(pObject));
}
template <class T> inline typename T::Ptr NotificationQueue<T>::dequeue(long timeout)
{
Poco::Notification::Ptr p = _queue.waitDequeueNotification(timeout);
if (!p)
throw Poco::TimeoutException("Dequeue timeout: " + std::string(type() == MediaType::VIDEO ? "Video" : "Audio"));
return p.cast<Notification<T>>()->get();
}
template <class T> inline void NotificationQueue<T>::clear()
{
_queue.clear();
}
template <class T> inline QueueStatus NotificationQueue<T>::status()
{
QueueStatus s = QueueStatus::NORMAL;
const auto size = _queue.size();
if (size >= _limit)
s = QueueStatus::FULL;
else if (size >= (_limit >> 1))
s = QueueStatus::OVERHALF;
return s;
}
template <class T> inline int NotificationQueue<T>::size()
{
return _queue.size();
}
}
}
#endif

View file

@ -0,0 +1,18 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Notifications.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
}
}

View file

@ -0,0 +1,39 @@
#ifndef EE166245_4519_4C28_AD01_A52B7613E95A
#define EE166245_4519_4C28_AD01_A52B7613E95A
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Component.h"
#include "util/Configuration.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class ParameterChangedNotification: public Component::Notification
{
public:
using Parameters = Util::Configuration::ParameterNames;
ParameterChangedNotification(const Parameters& parameters): _parameters(parameters)
{
}
const Parameters& parameters() const
{
return _parameters;
}
private:
Parameters _parameters;
};
}
}
#endif

137
video/core/Packet.cpp Normal file
View file

@ -0,0 +1,137 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Packet.h"
#include "Logger.h"
#include <string.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
const uint32_t Packet::FLAG_IS_VIDEO = 0x80000000;
const uint32_t Packet::FLAG_IS_KEY = 0x00000001;
const uint32_t Packet::FLAG_IS_AAC_HEADER = 0x00000002;
const uint32_t Packet::FLAG_IS_SEI = 0x04000000;
Packet::Packet(): _flags(0), _pts(0), _dts(0), _pData(nullptr), _length(0), _pAVPacket(nullptr)
{
}
Packet::Packet(const SerialCode& code, uint32_t flags, uint8_t* data, uint32_t length, int64_t pts, int64_t dts)
: _code(code), _flags(flags), _pts(pts), _dts(dts), _pData(data), _length(length), _pAVPacket(nullptr)
{
}
#if defined(USING_FFMPEG)
Packet::Packet(const SerialCode& code, bool isVideo, AVPacket** packet)
: _code(code), _pts((*packet)->pts), _dts((*packet)->dts), _pData(nullptr), _length((*packet)->size),
_pAVPacket(*packet)
{
_flags = 0;
if ((*packet)->flags & AV_PKT_FLAG_KEY)
_flags |= Packet::FLAG_IS_KEY;
if (isVideo)
_flags |= Packet::FLAG_IS_VIDEO;
*packet = nullptr;
}
#endif
Packet::Packet(const Packet& other)
{
_code = other._code;
_flags = other._flags;
_pData = other._pData;
_length = other._length;
_pts = other._pts;
_dts = other._dts;
#if defined(USING_FFMPEG)
if (other._pAVPacket)
_pAVPacket = av_packet_clone(other._pAVPacket);
else
_pAVPacket = nullptr;
#endif
}
Packet::~Packet()
{
#if defined(USING_FFMPEG)
if (_pAVPacket)
av_packet_free(&_pAVPacket);
#endif
}
#if defined(USING_FFMPEG)
void Packet::moveTo(AVPacket** packet)
{
if (_pAVPacket)
{
*packet = _pAVPacket;
_pData = nullptr;
_length = 0;
_pAVPacket = nullptr;
}
else
{
*packet = buildAVPacket();
_pData = nullptr;
_length = 0;
}
}
#endif
const uint8_t* Packet::data()
{
#if defined(USING_FFMPEG)
if (_pAVPacket)
return _pAVPacket->data;
#endif
return _pData.get();
}
#if defined(USING_FFMPEG)
AVPacket* Packet::buildAVPacket()
{
AVPacket* pPacket = av_packet_alloc();
pPacket->pts = _pts;
pPacket->dts = _dts;
if (_flags & FLAG_IS_KEY)
pPacket->flags |= AV_PKT_FLAG_KEY;
else
pPacket->flags &= ~AV_PKT_FLAG_KEY;
convertToAVPacket(pPacket);
return pPacket;
}
void Packet::convertToAVPacket(AVPacket* pPacket)
{
if (_pData != nullptr)
{
uint8_t* data = (uint8_t*)av_malloc(_length);
memcpy(data, _pData, _length);
int rc = av_packet_from_data(pPacket, data, _length);
if (rc < 0)
{
logger().error("Create av-packet FAIL!");
av_free(data);
}
_pData.reset();
}
else
throw Poco::DataException("Media::Packet::buildAVPacket: No source data");
}
#endif
}
}

126
video/core/Packet.h Normal file
View file

@ -0,0 +1,126 @@
#ifndef AE3F35DC_04CA_417F_8D57_CADC8913678F
#define AE3F35DC_04CA_417F_8D57_CADC8913678F
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "NotificationQueue.h"
#include <Poco/SharedPtr.h>
#include "SerialCode.h"
#if defined(USING_FFMPEG)
extern "C"
{
#include <libavcodec/packet.h>
#include <libavutil/mem.h>
}
#endif
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Packet
{
public:
using Ptr = Poco::SharedPtr<Packet>;
static const uint32_t FLAG_IS_VIDEO;
static const uint32_t FLAG_IS_KEY;
static const uint32_t FLAG_IS_AAC_HEADER;
static const uint32_t FLAG_IS_SEI;
public:
Packet();
Packet(const SerialCode& code, uint32_t flags, uint8_t* data, uint32_t length, int64_t pts, int64_t dts);
#if defined(USING_FFMPEG)
Packet(const SerialCode& code, bool isVideo, AVPacket** packet);
#endif
Packet(const Packet& other);
virtual ~Packet();
virtual void moveTo(AVPacket** packet);
bool isVideo() const;
bool isKeyFrame() const;
Ptr clone();
const SerialCode& serialCode();
const uint8_t* data();
uint32_t length();
uint64_t pts();
uint64_t dts();
uint32_t flags();
Poco::SharedPtr<uint8_t>& sharedData();
protected:
#if defined(USING_FFMPEG)
AVPacket* buildAVPacket();
virtual void convertToAVPacket(AVPacket* pPacket);
#endif
private:
SerialCode _code;
uint32_t _flags;
int64_t _pts, _dts;
Poco::SharedPtr<uint8_t> _pData;
uint32_t _length;
#if defined(USING_FFMPEG)
AVPacket* _pAVPacket;
#endif
};
using PacketQueue = NotificationQueue<Packet>;
inline bool Packet::isVideo() const
{
return (_flags & FLAG_IS_VIDEO) > 0;
}
inline bool Packet::isKeyFrame() const
{
return (_flags & FLAG_IS_KEY) > 0;
}
inline Packet::Ptr Packet::clone()
{
return new Packet(*this);
}
inline uint32_t Packet::length()
{
return _length;
}
inline uint64_t Packet::pts()
{
return _pts;
}
inline uint64_t Packet::dts()
{
return _dts;
}
inline uint32_t Packet::flags()
{
return _flags;
}
inline const SerialCode& Packet::serialCode()
{
return _code;
}
inline Poco::SharedPtr<uint8_t>& Packet::sharedData()
{
return _pData;
}
}
}
#endif

119
video/core/SampleFormat.cpp Normal file
View file

@ -0,0 +1,119 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "SampleFormat.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* TYPEDEFS
*******************************************************************************
*/
struct SampleFormatInfo
{
std::string name;
int bits;
bool isPlanar;
SampleFormat altFormat;
};
/*
*******************************************************************************
* LOCAL VARIABLES
*******************************************************************************
*/
static const SampleFormatInfo sampleFormatInfo[(int)SampleFormat::NB] = {
{"u8", 8, false, SampleFormat::U8P}, {"s16", 16, false, SampleFormat::S16P},
{"s32", 32, false, SampleFormat::S32P}, {"s64", 64, false, SampleFormat::S64P},
{"flt", 32, false, SampleFormat::FLTP}, {"dbl", 64, false, SampleFormat::DBLP},
{"u8p", 8, true, SampleFormat::U8}, {"s16p", 16, true, SampleFormat::S16},
{"s32p", 32, true, SampleFormat::S32}, {"s64p", 64, true, SampleFormat::S64},
{"fltp", 32, true, SampleFormat::FLT}, {"dblp", 64, true, SampleFormat::DBL},
};
static const std::string UNKNOWN = "UNKNOWN";
/*
*******************************************************************************
* LOCAL FUNCTIONS
*******************************************************************************
*/
static inline bool isSampleFormatValid(SampleFormat format)
{
return ((int)format >= 0 && format < SampleFormat::NB);
}
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
const std::string& sampleFormatName(SampleFormat format)
{
if (isSampleFormatValid(format))
return sampleFormatInfo[(int)format].name;
return UNKNOWN;
}
int sampleFormatBitWidth(SampleFormat format)
{
if (isSampleFormatValid(format))
return sampleFormatInfo[(int)format].bits;
return 0;
}
int sampleFormatBytesPerSample(SampleFormat format)
{
if (isSampleFormatValid(format))
return sampleFormatInfo[(int)format].bits >> 3;
return 0;
}
bool sampleFormatIsPlanar(SampleFormat format)
{
if (isSampleFormatValid(format))
return sampleFormatInfo[(int)format].isPlanar;
return false;
}
int sampleFormatCalcLineSize(SampleFormat format, ChannelLayout layout, int samples)
{
int size = sampleFormatBytesPerSample(format) * samples;
if (!sampleFormatIsPlanar(format))
size *= channelLayoutChannels(layout);
return size;
}
SampleFormat sampleFormatGetPackedFormat(SampleFormat format)
{
if (!isSampleFormatValid(format))
return SampleFormat::NONE;
if (!sampleFormatIsPlanar(format))
return format;
return sampleFormatInfo[(int)format].altFormat;
}
SampleFormat sampleFormatGetPlanarFormat(SampleFormat format)
{
if (!isSampleFormatValid(format))
return SampleFormat::NONE;
if (sampleFormatIsPlanar(format))
return format;
return sampleFormatInfo[(int)format].altFormat;
}
}
}

50
video/core/SampleFormat.h Normal file
View file

@ -0,0 +1,50 @@
#ifndef D05E30E1_E0D8_43BB_A05A_20ABEF7E2072
#define D05E30E1_E0D8_43BB_A05A_20ABEF7E2072
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "ChannelLayout.h"
#include <string>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
enum class SampleFormat
{
NONE = -1,
U8,
S16,
S32,
S64,
FLT,
DBL,
U8P,
S16P,
S32P,
S64P,
FLTP,
DBLP,
NB,
};
const std::string& sampleFormatName(SampleFormat format);
int sampleFormatBitWidth(SampleFormat format);
int sampleFormatBytesPerSample(SampleFormat format);
bool sampleFormatIsPlanar(SampleFormat format);
int sampleFormatCalcLineSize(SampleFormat format, ChannelLayout layout, int samples);
SampleFormat sampleFormatGetPackedFormat(SampleFormat format);
SampleFormat sampleFormatGetPlanarFormat(SampleFormat format);
}
}
#endif

107
video/core/SerialCode.cpp Normal file
View file

@ -0,0 +1,107 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "SerialCode.h"
#include <Poco/Random.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
bool SerialCode::empty() const
{
return _value.isNull();
}
void SerialCode::reset()
{
return _value.clear();
}
const SerialCode& SerialCode::operator++()
{
if (empty())
_value = 0;
++_value;
return *this;
}
const SerialCode& SerialCode::operator++(int)
{
if (empty())
_value = 0;
_value++;
return *this;
}
bool SerialCode::operator!=(const SerialCode& other)
{
return _value != other._value;
}
const SerialCode& SerialCode::operator=(const SerialCode& other)
{
_value = other._value;
return *this;
}
const SerialCode& SerialCode::operator=(const uint32_t value)
{
_value = value;
return *this;
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
SerialCodeManager::SerialCodeManager()
{
Poco::Random r;
r.seed();
_code = r.next();
}
void SerialCodeManager::increment()
{
_code++;
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
bool SerialCodeWatcher::isChanged(const SerialCode& code, bool firstAsChanged)
{
if (_code.empty())
{
_code = code;
return firstAsChanged;
}
if (_code != code)
{
_code = code;
return true;
}
return false;
}
void SerialCodeWatcher::reset()
{
_code.reset();
}
}
}

77
video/core/SerialCode.h Normal file
View file

@ -0,0 +1,77 @@
#ifndef F54A2617_3BA7_4779_88A5_AB040FD9B002
#define F54A2617_3BA7_4779_88A5_AB040FD9B002
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "CodecID.h"
#include <Poco/Nullable.h>
#include <Poco/Any.h>
#include <map>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class SerialCode
{
public:
bool empty() const;
void reset();
const SerialCode& operator++();
const SerialCode& operator++(int);
bool operator!=(const SerialCode& other);
const SerialCode& operator=(const SerialCode& other);
const SerialCode& operator=(const uint32_t other);
private:
Poco::Nullable<uint32_t> _value;
};
class SerialCodeManager
{
public:
SerialCodeManager();
template <class T> void put(const std::string& name, const T& value);
void increment();
const SerialCode& code() const;
private:
SerialCode _code;
std::map<std::string, Poco::Any> _values;
};
template <class T> inline void SerialCodeManager::put(const std::string& name, const T& value)
{
if (_values.find(name) == _values.end() || Poco::AnyCast<T>(_values[name]) != value)
{
_values[name] = value;
_code++;
}
}
inline const SerialCode& SerialCodeManager::code() const
{
return _code;
}
class SerialCodeWatcher
{
public:
bool isChanged(const SerialCode& code, bool firstAsChanged = false);
void reset();
private:
SerialCode _code;
};
}
}
#endif

37
video/core/System.cpp Normal file
View file

@ -0,0 +1,37 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "System.h"
#include <Poco/Exception.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
System* System::_pInstance = nullptr;
System::System()
{
}
System::~System()
{
delete _pInstance;
_pInstance = nullptr;
}
uint64_t System::timestamp()
{
if (!_pInstance)
throw Poco::IllegalStateException("Unregister implement class.");
return _pInstance->getTimestamp();
}
}
}

45
video/core/System.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef BC5898D8_C525_4FD9_9609_B2112CB1BE92
#define BC5898D8_C525_4FD9_9609_B2112CB1BE92
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include <Poco/Poco.h>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class System
{
protected:
System();
public:
virtual ~System();
public:
static uint64_t timestamp();
template <class T> static void registerClass();
protected:
virtual uint64_t getTimestamp() = 0;
private:
static System* _pInstance;
};
template <class T> inline void System::registerClass()
{
_pInstance = new T();
}
}
}
#endif

View file

@ -0,0 +1,31 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioComponent.h"
#include "VideoComponent.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Core::VideoComponent::VideoComponent(const std::string& name): Component(name)
{
setWidth(1920);
setHeight(1080);
setFrameRate(30);
setVideoScanMode(VideoScanMode::PROGRESSIVE);
setSignalLost(true);
}
VideoComponent::~VideoComponent()
{
}
}
}

View file

@ -0,0 +1,45 @@
#ifndef CAD9434C_3708_4C44_8A9D_2E146CFECEAC
#define CAD9434C_3708_4C44_8A9D_2E146CFECEAC
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Component.h"
#include "CodecID.h"
#include "ChannelLayout.h"
#include "CodecProfile.h"
#include "BitrateControl.h"
#include "GOPMode.h"
#include "SampleFormat.h"
#include "VideoScanMode.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class VideoComponent: public Component
{
public:
using Ptr = Poco::AutoPtr<VideoComponent>;
public:
VideoComponent(const std::string& name);
virtual ~VideoComponent();
virtual void reset() {};
public:
CONFIGURABLE_DECLARE_PARAMETER(Width, uint32_t);
CONFIGURABLE_DECLARE_PARAMETER(Height, uint32_t);
CONFIGURABLE_DECLARE_PARAMETER(FrameRate, int);
CONFIGURABLE_DECLARE_PARAMETER(VideoScanMode, VideoScanMode);
CONFIGURABLE_DECLARE_PARAMETER(SignalLost, bool);
};
}
}
#endif

125
video/core/VideoDecoder.cpp Normal file
View file

@ -0,0 +1,125 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoDecoder.h"
#include "Logger.h"
#include "Demuxer.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoDecodeHandler::VideoDecodeHandler(const std::string& name, CodecID codecID, VideoComponent::Ptr pPrevious)
: _name(name), _pPrevious(pPrevious)
{
}
VideoDecodeHandler::~VideoDecodeHandler()
{
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoDecoder::VideoDecoder(const std::string& name): VideoComponent(name)
{
}
VideoDecoder::~VideoDecoder()
{
}
void VideoDecoder::start()
{
_task.run(
[this](std::atomic<bool>& isCancelled) -> void
{
VideoDecodeHandler::Ptr pHandler;
while (!isCancelled)
{
try
{
Frame::Ptr pFrame = nullptr;
do
{
if (pHandler.isNull())
{
pFrame = pHandler->read();
if (!pFrame.isNull())
break;
}
Packet::Ptr pPacket = readPacket(1000);
if (serialCodeWatcher().isChanged(pPacket->serialCode(), true))
{
auto pPrevious = previousComponents().first();
auto pDemuxer = pPrevious.cast<Demuxer>();
if (pDemuxer)
{
auto pEncoder = pDemuxer->videoEncoder();
pEncoder->config().copyTo(config());
serialCodeManager().increment();
pHandler = VideoDecodeHandler::create(pEncoder->getCodecID(), pEncoder);
}
else
throw Poco::NotImplementedException(
Poco::format("Unsupported previous component: %s", pPrevious->name()));
}
pHandler->write(pPacket);
pFrame = pHandler->read();
} while (false);
if (!pFrame)
continue;
postToNextComponents(sharedFromThis(), pFrame, MediaType::VIDEO);
}
catch (Poco::Exception& e)
{
logger().error("%s: %s", name(), e.displayText());
Poco::Thread::sleep(1000);
}
catch (std::exception& e)
{
logger().error("%s: %s", name(), std::string(e.what()));
Poco::Thread::sleep(1000);
}
}
});
}
void VideoDecoder::stop()
{
_task.cancel();
}
Packet::Ptr VideoDecoder::readPacket(uint32_t timeout)
{
if (packetQueues().empty())
throw Poco::NotFoundException("Packet queues is empty.");
return packetQueues().first()->dequeue(timeout);
}
void VideoDecoder::onLink(Core::Component::Ptr pPrevious)
{
packetQueues().add(pPrevious, name(), MediaType::VIDEO);
}
void VideoDecoder::onUnlink(Core::Component::Ptr pPrevious)
{
packetQueues().remove(pPrevious);
}
}
}

115
video/core/VideoDecoder.h Normal file
View file

@ -0,0 +1,115 @@
#ifndef ACE5DB6F_B3E6_42E2_8DAE_6D520F93DC50
#define ACE5DB6F_B3E6_42E2_8DAE_6D520F93DC50
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoComponent.h"
#include "AudioDecoder.h"
#include "CodecID.h"
#include "util/FactoryManager.h"
#include "util/Task.h"
#include <Poco/SharedPtr.h>
#include <functional>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class VideoDecodeHandlerSelector
{
public:
static CodecID select(CodecID codec, VideoComponent::Ptr)
{
return codec;
}
};
class VideoDecodeHandler: public ::Util::FactoryManager<VideoDecodeHandler, CodecID, VideoDecodeHandlerSelector,
CodecID, VideoComponent::Ptr>
{
public:
using Ptr = Poco::SharedPtr<VideoDecodeHandler>;
public:
VideoDecodeHandler(const std::string& name, CodecID codecID, VideoComponent::Ptr pPrevious);
virtual ~VideoDecodeHandler();
public:
const std::string& name() const;
VideoComponent::Ptr previous() const;
virtual void write(Packet::Ptr pPacket) = 0;
virtual Frame::Ptr read() = 0;
private:
const std::string _name;
VideoComponent::Ptr _pPrevious;
};
inline const std::string& VideoDecodeHandler::name() const
{
return _name;
}
inline VideoComponent::Ptr VideoDecodeHandler::previous() const
{
return _pPrevious;
}
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class VideoDecoderSelector
{
public:
static int select(const std::string&)
{
return 0;
}
};
class VideoDecoder: public VideoComponent,
public ::Util::FactoryManager<VideoDecoder, int, VideoDecoderSelector, const std::string&>
{
public:
using Ptr = Poco::AutoPtr<VideoDecoder>;
public:
VideoDecoder(const std::string& name);
virtual ~VideoDecoder();
virtual void reset() {};
protected:
void start() override;
void stop() override;
virtual Frame::Ptr decode() = 0;
Packet::Ptr readPacket(uint32_t timeout = 4000);
void onLink(Core::Component::Ptr pPrevious) override;
void onUnlink(Core::Component::Ptr pPrevious) override;
Util::Task& task();
PROPERTY_DECLARE(AssociatedAudio, AudioDecoder::Ptr, nullptr, public, public)
private:
Util::Task _task;
};
inline Util::Task& VideoDecoder::task()
{
return _task;
}
}
}
#endif

View file

@ -0,0 +1,87 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoEncoder.h"
#include "Logger.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoEncoder::VideoEncoder(const std::string& name, CodecID codecID): VideoComponent(name)
{
setCodecID(codecID);
setCodecProfile(CodecProfile::H264_MAIN);
setBitrate(4000000);
setGOP(30);
setGOPMode(GOPMode::NORMAL_P);
setBitrateControl(BitrateControl::VBR);
setAdaptable(false);
}
VideoEncoder::~VideoEncoder()
{
}
void VideoEncoder::start()
{
_task.run(
[this](std::atomic<bool>& isCancelled) -> void
{
while (!isCancelled)
{
try
{
postToNextComponents(sharedFromThis(), encode(readFrame()), MediaType::VIDEO);
}
catch (Poco::Exception& e)
{
logger().error("%s: %s", name(), e.displayText());
Poco::Thread::sleep(100);
}
catch (std::exception& e)
{
logger().error("%s: %s", name(), std::string(e.what()));
Poco::Thread::sleep(100);
}
}
});
}
void VideoEncoder::stop()
{
_task.cancel();
}
Packet::Ptr VideoEncoder::encode(Frame::Ptr pFrame)
{
return nullptr;
}
Frame::Ptr VideoEncoder::readFrame(uint32_t timeout)
{
if (previousComponents().size() == 0)
throw Poco::NotFoundException("No previous components");
return frameQueues().first()->dequeue(timeout);
}
void VideoEncoder::onLink(Core::Component::Ptr pPrevious)
{
frameQueues().add(pPrevious, name(), MediaType::VIDEO);
}
void VideoEncoder::onUnlink(Core::Component::Ptr pPrevious)
{
frameQueues().remove(pPrevious);
}
}
}

75
video/core/VideoEncoder.h Normal file
View file

@ -0,0 +1,75 @@
#ifndef ED3217CD_F637_44D5_A6D9_E7AE0B9C3A4D
#define ED3217CD_F637_44D5_A6D9_E7AE0B9C3A4D
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoComponent.h"
#include "CodecID.h"
#include "util/FactoryManager.h"
#include "util/Task.h"
#include <Poco/SharedPtr.h>
#include <functional>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class VideoEncoderSelector
{
public:
static CodecID select(CodecID codecID)
{
return codecID;
}
};
class VideoEncoder: public VideoComponent,
public ::Util::FactoryManager<VideoEncoder, CodecID, VideoEncoderSelector, CodecID>
{
public:
using Ptr = Poco::AutoPtr<VideoEncoder>;
public:
VideoEncoder(const std::string& name, CodecID codecID);
virtual ~VideoEncoder();
public:
CONFIGURABLE_DECLARE_PARAMETER(CodecID, CodecID);
CONFIGURABLE_DECLARE_PARAMETER(CodecProfile, CodecProfile);
CONFIGURABLE_DECLARE_PARAMETER(Bitrate, int64_t);
CONFIGURABLE_DECLARE_PARAMETER(GOP, int);
CONFIGURABLE_DECLARE_PARAMETER(GOPMode, GOPMode);
CONFIGURABLE_DECLARE_PARAMETER(BitrateControl, BitrateControl);
CONFIGURABLE_DECLARE_PARAMETER(Adaptable, bool);
protected:
void start() override;
void stop() override;
virtual Packet::Ptr encode(Frame::Ptr pFrame);
Frame::Ptr readFrame(uint32_t timeout = 4000);
void onLink(Core::Component::Ptr pPrevious) override;
void onUnlink(Core::Component::Ptr pPrevious) override;
Util::Task& task();
private:
Util::Task _task;
};
inline Util::Task& VideoEncoder::task()
{
return _task;
}
}
}
#endif

99
video/core/VideoMixer.cpp Normal file
View file

@ -0,0 +1,99 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoMixer.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoMixer::Channel::Channel(const std::string& name, VideoMixer::Ptr pMixer, uint32_t id)
: VideoComponent(Poco::format(name + "/CH#%u", id)), _pMixer(pMixer), _id(id)
{
Region region = {
0.0,
0.0f,
1.0f,
1.0f,
};
setRegion(region);
setAttach(false);
setInput(nullptr);
setZOrder(0);
}
VideoMixer::Channel::~Channel()
{
}
void VideoMixer::Channel::onConfigChanged(ParameterNames& names)
{
if (names.exists(_Input))
{
unlink(true, false);
if (getAttach())
{
auto pInput = getInput();
if (pInput)
{
getInput()->link(sharedFromThis());
link(mixer());
}
return;
}
}
if (names.exists(_Attach))
{
auto pInput = getInput();
if (pInput)
{
if (getAttach())
{
getInput()->link(sharedFromThis());
link(mixer());
}
else
getInput()->unlink(sharedFromThis());
}
}
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoMixer::VideoMixer(const std::string& name): VideoComponent(name)
{
}
VideoMixer::~VideoMixer()
{
}
VideoMixer::Channel::Ptr VideoMixer::getChannel(uint32_t id)
{
if (_channels.find(id) == _channels.end())
_channels[id] = createChannel(id);
return _channels[id];
}
void VideoMixer::start()
{
}
void VideoMixer::stop()
{
}
}
}

118
video/core/VideoMixer.h Normal file
View file

@ -0,0 +1,118 @@
#ifndef D84CC9FA_711B_4439_A0F8_424C43E082CB
#define D84CC9FA_711B_4439_A0F8_424C43E082CB
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoComponent.h"
#include "util/FactoryManager.h"
#include <unordered_map>
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class VideoMixerSelector
{
public:
static int select(const std::string&)
{
return 0;
}
};
class VideoMixer: public VideoComponent,
public ::Util::FactoryManager<VideoMixer, int, VideoMixerSelector, const std::string&>
{
public:
using Ptr = Poco::AutoPtr<VideoMixer>;
class Channel: public VideoComponent
{
public:
using Ptr = Poco::AutoPtr<Channel>;
struct Region
{
double x;
double y;
double width;
double height;
bool operator==(const Region& other) const
{
return std::abs(x - other.x) < 1e-9 && std::abs(y - other.y) < 1e-9 &&
std::abs(width - other.width) < 1e-9 && std::abs(height - other.height) < 1e-9;
}
bool operator!=(const Region& other) const
{
return !(this->operator==(other));
}
};
protected:
Channel(const std::string& name, VideoMixer::Ptr, uint32_t id);
public:
~Channel();
CONFIGURABLE_DECLARE_PARAMETER(Region, Region)
CONFIGURABLE_DECLARE_PARAMETER(ZOrder, int)
CONFIGURABLE_DECLARE_PARAMETER(Attach, bool)
CONFIGURABLE_DECLARE_PARAMETER(Input, VideoComponent::Ptr)
uint32_t id() const;
protected:
void onConfigChanged(ParameterNames& names) override;
VideoMixer::Ptr mixer();
private:
VideoMixer::Ptr _pMixer;
uint32_t _id;
friend VideoMixer;
};
public:
VideoMixer(const std::string& name);
~VideoMixer();
Channel::Ptr getChannel(uint32_t id);
protected:
void start() override;
void stop() override;
virtual Channel::Ptr createChannel(uint32_t id) = 0;
template <class T, typename F> void foreachChannel(F&&);
private:
std::unordered_map<uint32_t, Channel::Ptr> _channels;
};
inline uint32_t VideoMixer::Channel::id() const
{
return _id;
}
inline VideoMixer::Ptr VideoMixer::Channel::mixer()
{
return _pMixer;
}
template <class T, typename F> inline void VideoMixer::foreachChannel(F&& exec)
{
for (auto m : _channels)
exec(m.second.cast<T>());
}
}
}
#endif

View file

@ -0,0 +1,25 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoOutput.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoOutput::VideoOutput(const std::string& name): VideoComponent(name)
{
}
VideoOutput::~VideoOutput()
{
}
}
}

62
video/core/VideoOutput.h Normal file
View file

@ -0,0 +1,62 @@
#ifndef BB15C48A_9D47_46F6_B428_E9355AD79570
#define BB15C48A_9D47_46F6_B428_E9355AD79570
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoComponent.h"
#include "util/FactoryManager.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class VideoOutput: public VideoComponent,
public ::Util::FactoryManager<VideoOutput, const std::string,
::Util::SimpleSelector<const std::string&>, const std::string&>
{
public:
using Ptr = Poco::AutoPtr<VideoOutput>;
enum class ColorType
{
RGB,
YUV,
};
enum class Mode
{
HDMI,
DVI,
};
VideoOutput(const std::string& name);
~VideoOutput();
CONFIGURABLE_DECLARE_PARAMETER(Timing, std::string)
CONFIGURABLE_DECLARE_PARAMETER(Mode, Mode)
CONFIGURABLE_DECLARE_PARAMETER(Luma, int)
CONFIGURABLE_DECLARE_PARAMETER(Contrast, int)
CONFIGURABLE_DECLARE_PARAMETER(Hue, int)
CONFIGURABLE_DECLARE_PARAMETER(Saturation, int)
CONFIGURABLE_DECLARE_PARAMETER(Mirror, int)
CONFIGURABLE_DECLARE_PARAMETER(Rotation, int)
CONFIGURABLE_DECLARE_PARAMETER(ColorType, ColorType)
};
}
}
#endif

View file

@ -0,0 +1,36 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoOverlay.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoOverlay::VideoOverlay(VideoOverlayType type, const std::string& name, const Rect& region)
: _type(type), _name(name), _region(region)
{
setZOrder(0);
setBlock(4);
}
VideoOverlay::~VideoOverlay()
{
}
void VideoOverlay::attach(VideoComponent::Ptr target)
{
if (target.isNull())
throw Poco::BugcheckException(name() + ": Target is NULL");
_target = target;
}
}
}

64
video/core/VideoOverlay.h Normal file
View file

@ -0,0 +1,64 @@
#ifndef CBC5C20D_24D0_4D1F_B00E_8299B36FD74E
#define CBC5C20D_24D0_4D1F_B00E_8299B36FD74E
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoComponent.h"
#include "GeometryTypes.h"
#include "Image.h"
#include "util/FactoryManager.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
enum class VideoOverlayType
{
CANVAS,
MOSAIC,
};
class VideoOverlaySelector
{
public:
static VideoOverlayType select(VideoOverlayType type, const std::string&, const Rect&)
{
return type;
}
};
class VideoOverlay: public ::Util::FactoryManager<VideoOverlay, VideoOverlayType, VideoOverlaySelector,
VideoOverlayType, const std::string&, const Rect&>,
public ::Util::Configurable
{
public:
using Ptr = Poco::SharedPtr<VideoOverlay>;
protected:
VideoOverlay(VideoOverlayType type, const std::string& name, const Rect& region);
public:
virtual ~VideoOverlay();
virtual void render(Image::Ptr pImage) = 0;
virtual void scrollRender(Image::Ptr pImage, int hscroll) = 0;
virtual void attach(VideoComponent::Ptr target);
CONFIGURABLE_DECLARE_PARAMETER(ZOrder, int)
CONFIGURABLE_DECLARE_PARAMETER(Block, int)
READONLY_PROPERTY_DECLARE(type, VideoOverlayType)
READONLY_PROPERTY_DECLARE(name, std::string)
READONLY_PROPERTY_DECLARE(target, VideoComponent::Ptr)
READONLY_PROPERTY_DECLARE(region, Rect)
};
}
}
#endif

View file

@ -0,0 +1,29 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoProcessor.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoProcessor::VideoProcessor(const std::string& name): Core::VideoComponent(name)
{
setRotation(0);
setCrop(Rect{0, 0, 0, 0});
setMirror(false);
setFlip(false);
}
VideoProcessor::~VideoProcessor()
{
}
}
}

View file

@ -0,0 +1,52 @@
#ifndef D5B26384_2A3D_4FBE_95FD_606CBAD0DA82
#define D5B26384_2A3D_4FBE_95FD_606CBAD0DA82
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoComponent.h"
#include "GeometryTypes.h"
#include "util/FactoryManager.h"
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
class VideoProcessorSelector
{
public:
static int select()
{
return 0;
}
};
class VideoProcessor: public VideoComponent, public ::Util::FactoryManager<VideoProcessor, int, VideoProcessorSelector>
{
public:
using Ptr = Poco::AutoPtr<VideoProcessor>;
public:
VideoProcessor(const std::string& name);
virtual ~VideoProcessor();
public:
CONFIGURABLE_DECLARE_PARAMETER(Rotation, int);
CONFIGURABLE_DECLARE_PARAMETER(Crop, Rect);
CONFIGURABLE_DECLARE_PARAMETER(Mirror, bool);
CONFIGURABLE_DECLARE_PARAMETER(Flip, bool);
CONFIGURABLE_DECLARE_PARAMETER(VideoDirection, int);
};
}
}
#endif

View file

@ -0,0 +1,22 @@
#ifndef A4F9D6E2_E09A_42F1_AE90_74D89A97557F
#define A4F9D6E2_E09A_42F1_AE90_74D89A97557F
namespace Video
{
namespace Core
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
enum class VideoScanMode
{
PROGRESSIVE,
INTERLACED,
NB,
};
}
}
#endif

128
video/fdk/AudioEncoder.cpp Normal file
View file

@ -0,0 +1,128 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioEncoder.h"
#include "Logger.h"
#include "../core/System.h"
#include "fdk-aac/FDK_audio.h"
namespace Video
{
namespace Fdk
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioEncoder::AudioEncoder(Core::CodecID codecID)
: Core::AudioEncoder(Poco::format("Fdk::AudioEncoder(%s)", Core::codecIDToString(codecID)), codecID),
_pEncoder(nullptr)
{
}
AudioEncoder::~AudioEncoder()
{
}
void AudioEncoder::initialize()
{
setSampleFormat(Core::SampleFormat::S16);
int channels = Core::channelLayoutChannels(getChannelLayout());
int samplerate = getSampleRate();
if (aacEncOpen(&_pEncoder, 0, channels) != AACENC_OK)
{
logger().error("Unable to open _pEncoder");
return;
}
if (aacEncoder_SetParam(_pEncoder, AACENC_CHANNELMODE, (channels == 1) ? MODE_1 : MODE_2) != AACENC_OK)
{
logger().error("Unable to set the channel mode");
return;
}
if (aacEncoder_SetParam(_pEncoder, AACENC_AOT, 2) != AACENC_OK)
{
logger().error("Unable to set the AOT");
return;
}
if (aacEncoder_SetParam(_pEncoder, AACENC_SAMPLERATE, samplerate) != AACENC_OK)
{
logger().error("Unable to set the SAMPLERATE");
return;
}
if (aacEncoder_SetParam(_pEncoder, AACENC_BITRATE, getBitrate()) != AACENC_OK)
{
logger().error("Unable to set the BITRATE");
return;
}
}
void AudioEncoder::deinitialize()
{
if (_pEncoder)
{
aacEncClose(&_pEncoder);
_pEncoder = nullptr;
}
}
Core::Packet::Ptr AudioEncoder::encode()
{
if (!_pEncoder)
return nullptr;
auto pIn = readFrame();
AACENC_BufDesc inBuf = {0}, outBuf = {0};
AACENC_InArgs inArgs = {0};
AACENC_OutArgs outArgs = {0};
uint8_t* pBuffer = new uint8_t[2048];
int inIdentifier = IN_AUDIO_DATA;
int inSize = pIn->length;
int inElemSize = 2;
int outIdentifier = OUT_BITSTREAM_DATA;
int outEemSize = 1;
int outSize = 2048;
inBuf.numBufs = 1;
uint8_t* data = pIn->data[0].get();
inBuf.bufs = (void**)&data;
inBuf.bufferIdentifiers = &inIdentifier;
inBuf.bufSizes = &inSize;
inBuf.bufElSizes = &inElemSize;
outBuf.numBufs = 1;
outBuf.bufs = (void**)&pBuffer;
outBuf.bufferIdentifiers = &outIdentifier;
outBuf.bufSizes = &outSize;
outBuf.bufElSizes = &outEemSize;
inArgs.numInSamples = pIn->length / inElemSize;
inArgs.numAncBytes = 0;
if (AACENC_OK != aacEncEncode(_pEncoder, &inBuf, &outBuf, &inArgs, &outArgs))
{
delete[] pBuffer;
return nullptr;
}
else
return new Core::Packet(serialCodeManager().code(), 0, pBuffer, outArgs.numOutBytes, pIn->timestampEncode,
pIn->timestampEncode);
}
void AudioEncoder::onConfigChanged(ParameterNames& names)
{
Core::AudioEncoder::onConfigChanged(names);
}
}
}

View file

@ -0,0 +1,119 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioDecodeHandler.h"
#include "Util.h"
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioDecodeHandler::AudioDecodeHandler(Core::CodecID codecID, Core::AudioComponent::Ptr pPrevious)
: Core::AudioDecodeHandler(Poco::format("FFmpeg::AudioDecodeHandler(%s)", Core::codecIDToString(codecID)), codecID,
pPrevious)
{
const AVCodec* pCodec = Util::getDecoder(codecID);
if (!pCodec)
throw Poco::NotImplementedException(
Poco::format("%s: Unsupported codec: %s", name(), Core::codecIDToString(codecID)), -EINVAL);
_pCodecCtx = avcodec_alloc_context3(pCodec);
if (!_pCodecCtx)
throw Poco::InvalidAccessException(Poco::format("%s: Could not alloc an encoding context", name()), -EACCES);
_pCodecCtx->ch_layout = Util::getChannelLayout(pPrevious->getChannelLayout());
_pCodecCtx->sample_rate = pPrevious->getSampleRate();
_pCodecCtx->sample_fmt = Util::getSampleFormat(pPrevious->getSampleFormat());
int rc = avcodec_open2(_pCodecCtx, pCodec, nullptr);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Open %s FAIL: %d(%s)", name(), std::string(pCodec->name), rc, Util::getErrorString(rc)),
rc);
_pFrame = av_frame_alloc();
if (Core::sampleFormatIsPlanar(pPrevious->getSampleFormat()))
{
const int c = Core::channelLayoutChannels(pPrevious->getChannelLayout());
switch (c)
{
case 1:
_frameConvertor = [](AVFrame* from, Core::Frame::Ptr to) -> void
{ memcpy(to->data[0], from->data[0], to->length); };
break;
case 2:
_frameConvertor = [](AVFrame* from, Core::Frame::Ptr to) -> void
{
memcpy(to->data[0], from->data[0], to->length);
memcpy(to->data[1], from->data[1], to->length);
};
break;
default:
throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL);
}
}
else
{
_frameConvertor = [](AVFrame* from, Core::Frame::Ptr to) -> void
{ memcpy(to->data[0], from->data[0], to->length); };
}
}
AudioDecodeHandler::~AudioDecodeHandler()
{
av_frame_free(&_pFrame);
avcodec_free_context(&_pCodecCtx);
}
void AudioDecodeHandler::write(Core::Packet::Ptr pPacket)
{
AVPacket* pAVPacket;
pPacket->moveTo(&pAVPacket);
int rc = avcodec_send_packet(_pCodecCtx, pAVPacket);
av_packet_free(&pAVPacket);
if (rc < 0)
throw Poco::RuntimeException(Poco::format("%s: Error submitting the packet to the decoder: %d(%s)", name(), rc,
Util::getErrorString(rc)),
rc);
}
Core::Frame::Ptr AudioDecodeHandler::read()
{
int rc = avcodec_receive_frame(_pCodecCtx, _pFrame);
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF)
return nullptr;
if (rc < 0)
throw Poco::RuntimeException(
Poco::format("%s: Error during decoding: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
return convert(_pFrame);
}
Core::Frame::Ptr AudioDecodeHandler::convert(AVFrame* pAVFrame)
{
int length = av_get_bytes_per_sample(_pCodecCtx->sample_fmt) * _pFrame->nb_samples;
Core::Frame::Ptr pFrame = new Core::Frame();
pFrame->length = length;
pFrame->timestamp = pAVFrame->pts;
for (int i = 0; i < pAVFrame->ch_layout.nb_channels; i++)
pFrame->data[i] = new uint8_t[length];
_frameConvertor(pAVFrame, pFrame);
return pFrame;
}
}
}

View file

@ -0,0 +1,44 @@
#ifndef AD6CD053_01C0_4CD4_B6DE_4F9D3940E437
#define AD6CD053_01C0_4CD4_B6DE_4F9D3940E437
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/AudioDecoder.h"
extern "C"
{
#include <libavcodec/avcodec.h>
}
#include <functional>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioDecodeHandler: public Core::AudioDecodeHandler
{
public:
AudioDecodeHandler(Core::CodecID codecID, Core::AudioComponent::Ptr pPrevious);
~AudioDecodeHandler();
protected:
void write(Core::Packet::Ptr pPacket) override;
Core::Frame::Ptr read() override;
Core::Frame::Ptr convert(AVFrame* pFrame);
private:
AVCodecContext* _pCodecCtx;
AVFrame* _pFrame;
AVPacket* _pPacket;
std::function<void(AVFrame*, Core::Frame::Ptr)> _frameConvertor;
};
}
}
#endif

View file

@ -0,0 +1,179 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioEncoder.h"
#include "Util.h"
#include "Logger.h"
#include "../core/System.h"
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioEncoder::AudioEncoder(Core::CodecID codecID)
: Core::AudioEncoder(Poco::format("FFmpeg::AudioEncoder(%s)", Core::codecIDToString(codecID)), codecID),
_pCodecCtx(nullptr), _pFrame(nullptr), _pPacket(nullptr)
{
}
AudioEncoder::~AudioEncoder()
{
}
void AudioEncoder::initialize()
{
auto codecID = getCodecID();
const AVCodec* pCodec = Util::getEncoder(codecID);
if (!pCodec)
throw Poco::NotImplementedException(
Poco::format("%s: Unsupported codec: %s", name(), Core::codecIDToString(codecID)), -EINVAL);
_pCodecCtx = avcodec_alloc_context3(pCodec);
if (!_pCodecCtx)
throw Poco::InvalidAccessException(Poco::format("%s: Could not alloc an encoding context", name()), -EACCES);
setChannelLayout(Core::ChannelLayout::STEREO);
switch (pCodec->id)
{
case AV_CODEC_ID_PCM_ALAW:
case AV_CODEC_ID_PCM_MULAW:
setSampleFormat(Core::SampleFormat::S16P);
setSampleRate(8000);
if (Core::channelLayoutChannels(getChannelLayout()) != 1)
setChannelLayout(Core::ChannelLayout::MONO);
setFrameSize(320);
break;
case AV_CODEC_ID_AAC:
setSampleFormat(Core::SampleFormat::FLTP);
setFrameSize(1024);
break;
case AV_CODEC_ID_MP3:
setSampleFormat(Core::SampleFormat::FLTP);
setFrameSize(1152);
break;
case AV_CODEC_ID_OPUS:
_pCodecCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
setSampleFormat(Core::SampleFormat::S16);
setFrameSize(960);
break;
default:
throw Poco::BugcheckException(
Poco::format("%s: Unspecified encoder(%s) parameters", name(), Core::codecIDToString(codecID)), -EINVAL);
}
_pCodecCtx->ch_layout = Util::getChannelLayout(getChannelLayout());
_pCodecCtx->sample_rate = getSampleRate();
_pCodecCtx->bit_rate = getBitrate();
_pCodecCtx->sample_fmt = Util::getSampleFormat(getSampleFormat());
_pCodecCtx->time_base = (AVRational){1, getSampleRate()};
int rc = avcodec_open2(_pCodecCtx, pCodec, nullptr);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Open %s FAIL: %d(%s)", name(), std::string(pCodec->name), rc, Util::getErrorString(rc)),
rc);
if (_pCodecCtx->frame_size == 0)
_pCodecCtx->frame_size = getFrameSize();
_pFrame = av_frame_alloc();
_pFrame->format = _pCodecCtx->sample_fmt;
_pFrame->sample_rate = _pCodecCtx->sample_rate;
_pFrame->nb_samples = _pCodecCtx->frame_size;
av_channel_layout_copy(&_pFrame->ch_layout, &_pCodecCtx->ch_layout);
av_frame_get_buffer(_pFrame, 0);
if (Core::sampleFormatIsPlanar(getSampleFormat()))
{
const int c = Core::channelLayoutChannels(getChannelLayout());
switch (c)
{
case 1:
_frameConvertor = [](Core::Frame::Ptr from, AVFrame* to) -> void
{ memcpy(to->data[0], from->data[0], from->length); };
break;
case 2:
_frameConvertor = [](Core::Frame::Ptr from, AVFrame* to) -> void
{
memcpy(to->data[0], from->data[0], from->length);
memcpy(to->data[1], from->data[1], from->length);
};
break;
default:
throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL);
}
}
else
{
_frameConvertor = [](Core::Frame::Ptr from, AVFrame* to) -> void
{ memcpy(to->data[0], from->data[0], from->length); };
}
}
void AudioEncoder::deinitialize()
{
av_frame_free(&_pFrame);
avcodec_free_context(&_pCodecCtx);
}
Core::Packet::Ptr AudioEncoder::encode()
{
if (_pPacket == nullptr)
_pPacket = av_packet_alloc();
int rc = avcodec_receive_packet(_pCodecCtx, _pPacket);
if (rc > 0)
{
return new Core::Packet(serialCodeManager().code(), false, &_pPacket);
}
while (true)
{
auto pIn = readFrame();
ts = pIn->timestamp;
rc = av_frame_make_writable(_pFrame);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Make frame writable failed: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
_frameConvertor(pIn, _pFrame);
_pFrame->pts = pIn->timestamp;
int rc = avcodec_send_frame(_pCodecCtx, _pFrame);
if (rc < 0)
throw Poco::InvalidAccessException(Poco::format("%s: Sending the frame to the encoder failed: %d(%s)",
name(), rc, Util::getErrorString(rc)),
rc);
rc = avcodec_receive_packet(_pCodecCtx, _pPacket);
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF)
continue;
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Error encoding audio frame: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
return new Core::Packet(serialCodeManager().code(), false, &_pPacket);
}
}
void AudioEncoder::onConfigChanged(ParameterNames& names)
{
Core::AudioEncoder::onConfigChanged(names);
}
}
}

View file

@ -0,0 +1,45 @@
#ifndef D5F58729_9077_452E_ADC5_2D75E17601EA
#define D5F58729_9077_452E_ADC5_2D75E17601EA
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/AudioEncoder.h"
extern "C"
{
#include <libavcodec/avcodec.h>
}
#include <functional>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioEncoder: public Core::AudioEncoder
{
public:
AudioEncoder(Core::CodecID codecID);
~AudioEncoder();
protected:
void initialize() override;
void deinitialize() override;
Core::Packet::Ptr encode() override;
void onConfigChanged(ParameterNames& names) override;
uint64_t ts = 0;
private:
AVCodecContext* _pCodecCtx;
AVFrame* _pFrame;
AVPacket* _pPacket;
std::function<void(Core::Frame::Ptr, AVFrame*)> _frameConvertor;
};
}
}
#endif

View file

@ -0,0 +1,181 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioResample.h"
#include "Util.h"
#include <Poco/Format.h>
#include <vector>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioResample::AudioResample(Core::ChannelLayout ilayout, Core::SampleFormat iformat, int irate,
Core::ChannelLayout olayout, Core::SampleFormat oformat, int orate)
: Core::AudioResample(Poco::format("FFmpeg::AudioResample(%s:%s:%d->%s:%s:%d)",
Core::channelLayoutToString(ilayout), Core::sampleFormatName(iformat), irate,
Core::channelLayoutToString(olayout), Core::sampleFormatName(oformat), orate),
ilayout, iformat, irate, olayout, oformat, orate),
_pSwrCtx(nullptr)
{
}
AudioResample::~AudioResample()
{
if (_pSwrCtx)
{
swr_close(_pSwrCtx);
swr_free(&_pSwrCtx);
}
}
Core::Frame::Ptr AudioResample::process(Core::Frame::Ptr in)
{
if (!_pSwrCtx)
{
auto iAVLayout = Util::getChannelLayout(inLayout());
auto oAVLayout = Util::getChannelLayout(outLayout());
auto iAVFormat = Util::getSampleFormat(inFormat());
auto oAVFormat = Util::getSampleFormat(outFormat());
int rc = swr_alloc_set_opts2(&_pSwrCtx, &oAVLayout, oAVFormat, outRate(), &iAVLayout, iAVFormat, inRate(), 0,
nullptr);
if (rc < 0)
throw Poco::InvalidArgumentException(
Poco::format("%s: Could not allocate SwrContext: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
if (outLayout() == Core::ChannelLayout::L || outLayout() == Core::ChannelLayout::R)
{
const int inCh = iAVLayout.nb_channels;
const int outCh = (int)oAVLayout.nb_channels;
const int srcCh = (outLayout() == Core::ChannelLayout::L) ? 0 : inCh - 1;
std::vector<double> matrix(inCh * outCh, 0.0);
matrix[srcCh] = 1.0;
rc = swr_set_matrix(_pSwrCtx, matrix.data(), inCh);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Could not set SwrContext matrix: %d(%s)", name(), rc, Util::getErrorString(rc)),
rc);
}
rc = swr_init(_pSwrCtx);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Could not init SwrContext: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
setAssignPointersMethod(_assignInPointers, inFormat(), inLayout());
setAssignPointersMethod(_assignOutPointers, outFormat(), outLayout());
if (av_sample_fmt_is_planar(iAVFormat))
_iFrameSize = in->length / av_get_bytes_per_sample(iAVFormat);
else
_iFrameSize = in->length / (av_get_bytes_per_sample(iAVFormat) * iAVLayout.nb_channels);
_oSampleSize = av_get_bytes_per_sample(oAVFormat);
if (!av_sample_fmt_is_planar(oAVFormat))
_oSampleSize *= oAVLayout.nb_channels;
_oFrameSize = av_rescale_rnd(swr_get_delay(_pSwrCtx, inRate()) + _iFrameSize, outRate(), inRate(), AV_ROUND_UP);
const int c = Core::channelLayoutChannels(outLayout());
if (Core::sampleFormatIsPlanar(outFormat()))
{
_oBytesPerChannel = av_samples_get_buffer_size(NULL, 1, _oFrameSize, oAVFormat, 1);
switch (c)
{
case 1:
_frameCreator = [this]() -> Core::Frame::Ptr
{
Core::Frame::Ptr pFrame = new Core::Frame;
pFrame->length = _oBytesPerChannel;
pFrame->data[0] = new uint8_t[_oBytesPerChannel << 1];
return pFrame;
};
break;
case 2:
_frameCreator = [this]() -> Core::Frame::Ptr
{
Core::Frame::Ptr pFrame = new Core::Frame;
pFrame->length = _oBytesPerChannel;
pFrame->data[0] = new uint8_t[_oBytesPerChannel << 1];
pFrame->data[1] = new uint8_t[_oBytesPerChannel << 1];
return pFrame;
};
break;
default:
throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL);
}
}
else
{
_oBytesPerChannel = av_samples_get_buffer_size(NULL, c, _oFrameSize, oAVFormat, 1);
_frameCreator = [this]() -> Core::Frame::Ptr
{
Core::Frame::Ptr pFrame = new Core::Frame;
pFrame->length = _oBytesPerChannel;
pFrame->data[0] = new uint8_t[_oBytesPerChannel << 1];
return pFrame;
};
}
}
Core::Frame::Ptr out = _frameCreator();
out->timestamp = in->timestamp;
uint8_t* iptrs[Core::Frame::DATA_LINES] = {nullptr};
uint8_t* optrs[Core::Frame::DATA_LINES] = {nullptr};
_assignInPointers(iptrs, in);
_assignOutPointers(optrs, out);
int rc = swr_convert(_pSwrCtx, optrs, _oFrameSize, (const uint8_t**)iptrs, _iFrameSize);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Convert FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
out->length = rc * _oSampleSize;
return out;
}
void AudioResample::setAssignPointersMethod(AssignPointersMethod& method, Video::Core::SampleFormat iformat,
Video::Core::ChannelLayout ilayout)
{
if (Core::sampleFormatIsPlanar(iformat))
{
const int c = Core::channelLayoutChannels(ilayout);
switch (c)
{
case 1:
method = [](uint8_t* ptrs[], Core::Frame::Ptr pFrame) -> void { ptrs[0] = pFrame->data[0].get(); };
break;
case 2:
method = [](uint8_t* ptrs[], Core::Frame::Ptr pFrame) -> void
{
ptrs[0] = pFrame->data[0].get();
ptrs[1] = pFrame->data[1].get();
};
break;
default:
throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL);
}
}
else
{
method = [](uint8_t* ptrs[], Core::Frame::Ptr pFrame) -> void { ptrs[0] = pFrame->data[0].get(); };
}
}
}
}

View file

@ -0,0 +1,52 @@
#ifndef B4406D43_D00D_4DB9_91AB_1428F3FB104C
#define B4406D43_D00D_4DB9_91AB_1428F3FB104C
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/AudioResample.h"
extern "C"
{
#include <libswresample/swresample.h>
}
#include <functional>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioResample: public Core::AudioResample
{
protected:
using AssignPointersMethod = std::function<void(uint8_t*[], Core::Frame::Ptr)>;
using FrameCreator = std::function<Core::Frame::Ptr(void)>;
public:
AudioResample(Core::ChannelLayout ilayout, Core::SampleFormat iformat, int irate, Core::ChannelLayout olayout,
Core::SampleFormat oformat, int orate);
~AudioResample();
public:
Core::Frame::Ptr process(Core::Frame::Ptr in) override;
void setAssignPointersMethod(AssignPointersMethod& method, Video::Core::SampleFormat iformat,
Video::Core::ChannelLayout ilayout);
private:
SwrContext* _pSwrCtx;
AssignPointersMethod _assignInPointers;
AssignPointersMethod _assignOutPointers;
FrameCreator _frameCreator;
int _oBytesPerChannel;
int _iFrameSize;
int _oFrameSize;
int _oSampleSize;
};
}
}
#endif

View file

@ -0,0 +1,311 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "DemuxHandler.h"
#include "Util.h"
#include "Logger.h"
#include "../core/System.h"
#include "util/Math.h"
#include <list>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
DemuxHandler::DemuxHandler(const std::string& uri)
: Core::DemuxHandler(Poco::format("FFmpeg::DemuxHandler(%s)", uri), uri)
{
}
DemuxHandler::~DemuxHandler()
{
}
void DemuxHandler::run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder, const Post& post,
std::atomic<bool>& isCancelled)
{
_pFormatCtx = avformat_alloc_context();
_pFormatCtx->interrupt_callback.callback = DemuxHandler::interruptCallback;
_pFormatCtx->interrupt_callback.opaque = this;
std::string url = uri();
bool isFileScheme = (scheme() == "file");
if (isFileScheme)
url = uri().substr(std::string("file:").size());
AVDictionary* pOptions = nullptr;
if (scheme() == "rtsp" && getUsingTCPTransport())
av_dict_set(&pOptions, "rtsp_transport", "tcp", 0);
av_dict_set(&pOptions, "stimeout", "2000000", 0);
setAnchorTime(Core::System::timestamp());
int rc = avformat_open_input(&_pFormatCtx, url.c_str(), nullptr, &pOptions);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Open input FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
if (!isFileScheme)
{
_pFormatCtx->probesize = 256 * 1024;
_pFormatCtx->max_analyze_duration = 300 * 1000;
}
logger().debug("avformat_find_stream_info enter");
rc = avformat_find_stream_info(_pFormatCtx, nullptr);
logger().debug("avformat_find_stream_info exit");
if (rc < 0)
{
avformat_close_input(&_pFormatCtx);
throw Poco::NotFoundException(
Poco::format("%s: Find stream FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
}
av_dump_format(_pFormatCtx, 0, url.c_str(), 0);
int vIndex = -1;
AVRational vTimebase;
AVCodecContext* pVidecCodecCtx = nullptr;
if (openCodec(AVMEDIA_TYPE_VIDEO, vIndex, &pVidecCodecCtx) >= 0)
{
auto pStream = _pFormatCtx->streams[vIndex];
int fr = av_q2d(pStream->r_frame_rate);
vTimebase = pStream->time_base;
pVideoEncoder->setCodecID(Util::getCodecID(pVidecCodecCtx->codec_id));
pVideoEncoder->setWidth(pVidecCodecCtx->width);
pVideoEncoder->setHeight(pVidecCodecCtx->height);
pVideoEncoder->setFrameRate(fr);
pVideoEncoder->setGOP(pVidecCodecCtx->gop_size);
pVideoEncoder->setBitrate(pVidecCodecCtx->bit_rate);
}
pVideoEncoder->setSignalLost(vIndex < 0);
int aIndex = -1;
AVRational aTimebase;
AVCodecContext* pAudioCodecCtx = nullptr;
if (openCodec(AVMEDIA_TYPE_AUDIO, aIndex, &pAudioCodecCtx) >= 0)
{
auto pStream = _pFormatCtx->streams[aIndex];
logger().information("%s: Audio found: %s, %dHz, frame-size: %d, %dKbps", name(),
Util::getCodecIDString(pAudioCodecCtx->codec_id), pAudioCodecCtx->sample_rate,
pAudioCodecCtx->frame_size, (int)((float)pAudioCodecCtx->bit_rate / 1000));
aTimebase = pStream->time_base;
pAudioEncoder->setCodecID(Util::getCodecID(pAudioCodecCtx->codec_id));
pAudioEncoder->setSampleFormat(Util::getSampleFormat(pAudioCodecCtx->sample_fmt));
pAudioEncoder->setBitrate(pAudioCodecCtx->bit_rate);
pAudioEncoder->setFrameSize(pAudioCodecCtx->frame_size);
pAudioEncoder->setChannelLayout(Util::getChannelLayout(pAudioCodecCtx->ch_layout));
pAudioEncoder->setSampleRate(pAudioCodecCtx->sample_rate);
}
pAudioEncoder->setSignalLost(aIndex < 0);
pAudioEncoder->commit();
if (vIndex < 0 && aIndex < 0)
{
avformat_close_input(&_pFormatCtx);
throw Poco::NotFoundException(Poco::format("%s: Could NOT find any stream", name()), -ENOSYS);
}
int primaryStream = vIndex >= 0 ? vIndex : aIndex;
AVBSFContext* pAVBSFContext = nullptr;
AVBitStreamFilter* pAVBitStreamFilter = nullptr;
if (vIndex >= 0)
{
if (pVidecCodecCtx->codec_id == AV_CODEC_ID_H264)
pAVBitStreamFilter = (AVBitStreamFilter*)av_bsf_get_by_name("h264_mp4toannexb");
else if (pVidecCodecCtx->codec_id == AV_CODEC_ID_H265)
pAVBitStreamFilter = (AVBitStreamFilter*)av_bsf_get_by_name("hevc_mp4toannexb");
if (pAVBitStreamFilter)
{
av_bsf_alloc(pAVBitStreamFilter, &pAVBSFContext);
avcodec_parameters_copy(pAVBSFContext->par_in, _pFormatCtx->streams[vIndex]->codecpar);
av_bsf_init(pAVBSFContext);
}
if (!pAVBSFContext)
{
avcodec_free_context(&pVidecCodecCtx);
avcodec_free_context(&pAudioCodecCtx);
avformat_close_input(&_pFormatCtx);
throw Poco::NullValueException(Poco::format("%s: Init 'AVBSFContext' FAIL", name()), -ENOSYS);
}
}
if (_seekReqeust)
{
if (isFileScheme && _seekTo < _pFormatCtx->duration)
{
rc = avformat_seek_file(_pFormatCtx, -1, INT64_MIN, _seekTo, INT64_MAX, AVSEEK_FLAG_FRAME);
if (rc < 0)
logger().error("%s: Seek file FAIL: %d(%s)", name(), rc, Util::getErrorString(rc));
else
logger().debug("%s: Seek file SUCCESS", name());
}
_seekReqeust = false;
}
AVPacket* pPacket = nullptr;
pVideoEncoder->commit();
logger().information("%s: Video found: %s, %dx%d@%d, gop: %d, %dKbps", name(),
Util::getCodecIDString(pVidecCodecCtx->codec_id), pVidecCodecCtx->width,
pVidecCodecCtx->height, pVideoEncoder->getFrameRate(), pVidecCodecCtx->gop_size,
(int)((float)pVidecCodecCtx->bit_rate / 1000));
auto postPacket = [&](AVPacket*& pAVPacket) -> void
{
if (pAVPacket->stream_index == primaryStream)
setPosition(
av_rescale_q(pAVPacket->pts, _pFormatCtx->streams[pAVPacket->stream_index]->time_base, {1, 1000000}));
if (pAVPacket->stream_index == vIndex)
{
rc = av_bsf_send_packet(pAVBSFContext, pAVPacket);
av_packet_unref(pAVPacket);
if (rc < 0)
{
logger().error("%s: Send BSF packet FAIL: %d(%s)", name(), rc, Util::getErrorString(rc));
return;
}
while (av_bsf_receive_packet(pAVBSFContext, pAVPacket) >= 0)
{
adjustTimestamp(pAVPacket, vTimebase);
updateTimeline(pAVPacket->pts);
post(new Core::Packet(pVideoEncoder->code(), true, &pAVPacket), Core::MediaType::VIDEO);
}
}
else if (pAVPacket->stream_index == aIndex)
{
adjustTimestamp(pAVPacket, aTimebase);
updateTimeline(pAVPacket->pts);
post(new Core::Packet(pAudioEncoder->code(), false, &pAVPacket), Core::MediaType::AUDIO);
}
else
av_packet_unref(pAVPacket);
};
while (!isCancelled)
{
setAnchorTime(Core::System::timestamp());
if (!pPacket)
pPacket = av_packet_alloc();
if (_seekReqeust)
{
if (isFileScheme && _seekTo < _pFormatCtx->duration)
{
rc = avformat_seek_file(_pFormatCtx, -1, INT64_MIN, _seekTo, INT64_MAX, AVSEEK_FLAG_FRAME);
if (rc < 0)
logger().error("%s: Seek file FAIL: %d(%s)", name(), rc, Util::getErrorString(rc));
else
{
logger().debug("%s: Seek file SUCCESS", name());
setBaseline(getTimeline());
resetTimeDrift();
}
}
_seekReqeust = false;
}
rc = av_read_frame(_pFormatCtx, pPacket);
if (rc < 0)
{
logger().error("%s: Read FAIL: %d(%s)", name(), rc, Util::getErrorString(rc));
break;
}
postPacket(pPacket);
}
av_bsf_free(&pAVBSFContext);
av_packet_free(&pPacket);
avcodec_free_context(&pVidecCodecCtx);
avcodec_free_context(&pAudioCodecCtx);
avformat_close_input(&_pFormatCtx);
}
int DemuxHandler::openCodec(AVMediaType mediaType, int& index, AVCodecContext** pCodecCtx)
{
index = -1;
int rc = av_find_best_stream(_pFormatCtx, mediaType, -1, -1, NULL, 0);
if (rc < 0)
{
logger().warning("%s: Could not find %s stream in input: %d(%s)", name(), Util::getMediaTypeString(mediaType),
rc, Util::getErrorString(rc));
return rc;
}
index = rc;
AVStream* pStream = _pFormatCtx->streams[index];
const AVCodec* pCodec = avcodec_find_decoder(pStream->codecpar->codec_id);
if (!pCodec)
{
logger().error("%s: Failed to find %s codec", name(), Util::getMediaTypeString(mediaType));
return AVERROR(EINVAL);
}
*pCodecCtx = avcodec_alloc_context3(pCodec);
if (!*pCodecCtx)
{
logger().error("%s: Failed to allocate the %s codec context", name(), Util::getMediaTypeString(mediaType));
return AVERROR(ENOMEM);
}
if ((rc = avcodec_parameters_to_context(*pCodecCtx, pStream->codecpar)) < 0)
{
logger().error("%s: Failed to copy %s codec parameters to decoder context: %d(%s)", name(),
Util::getMediaTypeString(mediaType), rc, Util::getErrorString(rc));
return rc;
}
if ((rc = avcodec_open2(*pCodecCtx, pCodec, NULL)) < 0)
{
logger().error("%s: Failed to open %s codec: %d(%s)", name(), Util::getMediaTypeString(mediaType), rc,
Util::getErrorString(rc));
return rc;
}
return 0;
}
void DemuxHandler::adjustTimestamp(AVPacket* pPacket, AVRational& timebase)
{
av_packet_rescale_ts(pPacket, timebase, {1, 1000000});
auto drift = getTimeDrift(pPacket->pts);
pPacket->pts += drift;
pPacket->dts += drift;
}
void DemuxHandler::updateTimeline(uint64_t ts)
{
if (getTimeline() < ts)
setTimeline(ts);
}
void DemuxHandler::seek(uint64_t ts)
{
_seekReqeust = true;
_seekTo = ts;
}
int DemuxHandler::interruptCallback(void* pCtx)
{
return 0;
}
}
}

View file

@ -0,0 +1,55 @@
#ifndef B6199A70_B55C_41FF_ABDD_96D9A657AA29
#define B6199A70_B55C_41FF_ABDD_96D9A657AA29
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/Demuxer.h"
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavcodec/bsf.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
}
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class DemuxHandler: public Core::DemuxHandler
{
public:
DemuxHandler(const std::string& url);
~DemuxHandler();
public:
void run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder, const Post& post,
std::atomic<bool>& isCancelled) override;
protected:
PROPERTY_DECLARE(AnchorTime, uint64_t, 0, protected, protected)
protected:
int openCodec(AVMediaType type, int& index, AVCodecContext** pCodecCtx);
void adjustTimestamp(AVPacket* pPacket, AVRational& timebase);
void updateTimeline(uint64_t ts);
void seek(uint64_t) override;
private:
AVFormatContext* _pFormatCtx;
std::atomic<bool> _seekReqeust;
int64_t _seekTo;
protected:
static int interruptCallback(void* pCtx);
};
}
}
#endif

295
video/ffmpeg/Muxer.cpp Normal file
View file

@ -0,0 +1,295 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Muxer.h"
#include "Util.h"
#include "Logger.h"
#include <Poco/Format.h>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Muxer::Stream::Stream(AVFormatContext& format, Core::VideoEncoder::Ptr pEncoder, AVRational tb,
Core::PacketQueue::Ptr pQueue)
: _pQueue(pQueue), _timebase(tb), _timeline(0), _pEncoder(nullptr)
{
open(format, pEncoder->getCodecID(),
[&](AVCodecContext* pCodecCtx, AVStream* pStream) -> void
{
int fps = pEncoder->getFrameRate();
pCodecCtx->width = pEncoder->getWidth();
pCodecCtx->height = pEncoder->getHeight();
pCodecCtx->time_base = {1, fps};
pCodecCtx->framerate = {fps, 1};
pCodecCtx->gop_size = pEncoder->getGOP();
pCodecCtx->max_b_frames = 0;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
pCodecCtx->bit_rate = pEncoder->getBitrate();
pStream->time_base = pCodecCtx->time_base;
pStream->r_frame_rate = pCodecCtx->framerate;
});
}
Muxer::Stream::Stream(AVFormatContext& format, Core::AudioEncoder::Ptr pEncoder, AVRational tb,
Core::PacketQueue::Ptr pQueue)
: _pQueue(pQueue), _timebase(tb), _timeline(0), _pEncoder(nullptr)
{
const Core::CodecID codecID = pEncoder->getCodecID();
open(format, codecID,
[&](AVCodecContext* pCodecCtx, AVStream* pStream) -> void
{
switch (codecID)
{
case Core::CodecID::G711A:
case Core::CodecID::G711U:
pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
pCodecCtx->ch_layout = AV_CHANNEL_LAYOUT_MONO;
break;
case Core::CodecID::MP3:
case Core::CodecID::AAC:
pCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
pCodecCtx->ch_layout = AV_CHANNEL_LAYOUT_STEREO;
break;
case Core::CodecID::OPUS:
pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
pCodecCtx->ch_layout = AV_CHANNEL_LAYOUT_STEREO;
break;
default:
throw Poco::NotImplementedException(
Poco::format("Unsupported codec: %s", Core::codecIDToString(codecID)), -EINVAL);
}
pCodecCtx->sample_rate = pEncoder->getSampleRate();
pCodecCtx->bit_rate = pEncoder->getBitrate();
pStream->time_base = (AVRational){1, pCodecCtx->sample_rate};
});
}
Muxer::Stream::~Stream()
{
close();
}
void Muxer::Stream::open(AVFormatContext& format, const Core::CodecID codec,
std::function<void(AVCodecContext*, AVStream*)> init)
{
_pEncoder = nullptr;
const AVCodec* pCodec = Util::getEncoder(codec);
if (!pCodec)
throw Poco::NotImplementedException(Poco::format("Unsupported codec: %s", Core::codecIDToString(codec)),
-EINVAL);
_pStream = avformat_new_stream(&format, pCodec);
if (!_pStream)
throw Poco::OutOfMemoryException("Alloc stream object FAIL", -ENOMEM);
_pStream->id = format.nb_streams - 1;
_pEncoder = avcodec_alloc_context3(pCodec);
if (!_pEncoder)
throw Poco::NotImplementedException(Poco::format("Unsupported codec: %s", Core::codecIDToString(codec)),
-EINVAL);
_pEncoder->codec_id = pCodec->id;
init(_pEncoder, _pStream);
if (format.oformat->flags & AVFMT_GLOBALHEADER)
_pEncoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
int rc = avcodec_open2(_pEncoder, pCodec, NULL);
if (rc < 0)
throw Poco::RuntimeException(
Poco::format("Open codec(%s) FAIL: %d(%s)", Core::codecIDToString(codec), rc, Util::getErrorString(rc)),
rc);
avcodec_parameters_from_context(_pStream->codecpar, _pEncoder);
}
void Muxer::Stream::close()
{
avcodec_free_context(&_pEncoder);
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Muxer::Muxer(const std::string& url): Core::Muxer(Poco::format("FFmpeg::Muxer(%s)", url), url)
{
}
Muxer::~Muxer()
{
}
void Muxer::connect()
{
const std::string format = getFormatName();
int rc =
avformat_alloc_output_context2(&_pFormatCtx, nullptr, format.empty() ? nullptr : format.c_str(), url().c_str());
if (rc < 0)
throw Poco::NotFoundException(Poco::format("%s: Create output format FAIL: %s: %s", name(), scheme(), format),
-EINVAL);
AVRational vtb, atb;
getSuitableTimebase(vtb, atb);
previousComponents().foreach (
[&](Core::Component::Ptr pPrevious) -> void
{
auto pVideoEncoder = pPrevious.cast<Core::VideoEncoder>();
if (!pVideoEncoder.isNull())
{
_streams.push_back(new Stream(*_pFormatCtx, pVideoEncoder, vtb, packetQueues().find(pPrevious)));
return;
}
auto pAudioEncoder = pPrevious.cast<Core::AudioEncoder>();
if (!pAudioEncoder.isNull())
{
_streams.push_back(new Stream(*_pFormatCtx, pAudioEncoder, atb, packetQueues().find(pPrevious)));
}
});
if (!(_pFormatCtx->oformat->flags & AVFMT_NOFILE))
{
AVDictionary* pOptions = nullptr;
av_dict_set(&pOptions, "rw_timeout", "10000", 0);
if ("udp" == scheme())
{
av_dict_set(&pOptions, "pkt_size", "1316", 0);
}
rc = avio_open2(&_pFormatCtx->pb, url().c_str(), AVIO_FLAG_WRITE, nullptr, &pOptions);
if (rc < 0)
throw Poco::RuntimeException(
Poco::format("%s: Open: '%s' FAIL: %s", name(), url(), Util::getErrorString(rc)), rc);
}
AVDictionary* pOptions = NULL;
if ("rtsp" == scheme())
{
av_dict_set(&pOptions, "rtsp_transport", "tcp", 0);
av_dict_set(&pOptions, "buffer_size", "1024000", 0);
}
rc = avformat_write_header(_pFormatCtx, &pOptions);
if (rc < 0)
throw Poco::RuntimeException(
Poco::format("%s: Open output: '%s' FAIL: %s", name(), url(), Util::getErrorString(rc)), rc);
}
void Muxer::mux()
{
static const AVRational timebase = {1, 1000000};
std::sort(_streams.begin(), _streams.end(), [](auto a, auto b) { return *a < *b; });
auto& pStream = *_streams.begin();
Core::Packet::Ptr pPacket = pStream->queue()->dequeue(4000);
if (pStream->serialCodeWatcher().isChanged(pPacket->serialCode()))
throw Poco::ApplicationException(Poco::format("%s: Stream changed, restart...", name()));
AVPacket* pAVPacket;
pPacket->moveTo(&pAVPacket);
pAVPacket->stream_index = pStream->id();
pStream->setTimeline(pAVPacket->pts);
av_packet_rescale_ts(pAVPacket, timebase, pStream->timebase());
int rc = av_interleaved_write_frame(_pFormatCtx, pAVPacket);
av_packet_free(&pAVPacket);
if (rc < 0)
{
logger().warning("%s: mux FAIL: %d (%s)", name(), rc, Util::getErrorString(rc));
if (rc != -22)
throw Poco::RuntimeException(Poco::format("%s: mux FAIL: %d (%s)", name(), rc, Util::getErrorString(rc)),
rc);
}
}
void Muxer::disconnect()
{
if (_pFormatCtx && _pFormatCtx->pb)
{
av_write_trailer(_pFormatCtx);
if (!(_pFormatCtx->oformat->flags & AVFMT_NOFILE))
avio_close(_pFormatCtx->pb);
}
_streams.clear();
avformat_free_context(_pFormatCtx);
}
const std::string Muxer::getFormatName()
{
const std::string s = scheme();
if (s == "rtmp")
return "flv";
if (s == "rtsp")
return "rtsp";
if (s == "udp")
return "mpegts";
return "";
}
void Muxer::getSuitableTimebase(AVRational& vtb, AVRational& atb)
{
const std::string s = scheme();
if ("rtmp" == s)
{
atb = {1, 1000};
vtb = {1, 1000};
}
else if ("rtsp" == s)
{
previousComponents().foreach (
[&](Core::Component::Ptr pPrevious) -> void
{
auto pEncoder = pPrevious.cast<Core::AudioEncoder>();
if (!pEncoder.isNull())
{
switch (pEncoder->getCodecID())
{
case Core::CodecID::AAC:
case Core::CodecID::OPUS:
atb = {1, pEncoder->getSampleRate()};
break;
case Core::CodecID::G711A:
case Core::CodecID::G711U:
atb = {1, 8000};
break;
default:
atb = {1, 90000};
break;
}
}
});
vtb = {1, 90000};
}
else if ("udp" == s)
{
atb = {1, 90000};
vtb = {1, 90000};
}
}
}
}

121
video/ffmpeg/Muxer.h Normal file
View file

@ -0,0 +1,121 @@
#ifndef DE90A7C0_21FB_4520_99C8_4A6D864A7F12
#define DE90A7C0_21FB_4520_99C8_4A6D864A7F12
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/Muxer.h"
#include "../core/VideoEncoder.h"
#include "../core/AudioEncoder.h"
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}
#include <functional>
#include <vector>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Muxer: public Core::Muxer
{
protected:
class Stream
{
public:
using Ptr = Poco::SharedPtr<Stream>;
public:
Stream(AVFormatContext& format, Core::VideoEncoder::Ptr pEncoder, AVRational tb, Core::PacketQueue::Ptr pQueue);
Stream(AVFormatContext& format, Core::AudioEncoder::Ptr pEncoder, AVRational tb, Core::PacketQueue::Ptr pQueue);
~Stream();
public:
int id() const;
bool operator<(const Stream& other);
Core::PacketQueue::Ptr queue();
AVRational timebase();
uint64_t getTimeline();
void setTimeline(uint64_t t);
Core::SerialCodeWatcher& serialCodeWatcher();
protected:
void open(AVFormatContext& format, const Core::CodecID codec,
std::function<void(AVCodecContext*, AVStream*)> init);
void close();
private:
AVStream* _pStream;
Core::PacketQueue::Ptr _pQueue;
AVRational _timebase;
uint64_t _timeline;
AVCodecContext* _pEncoder;
Core::SerialCodeWatcher _serialCodeWatcher;
};
public:
Muxer(const std::string& url);
~Muxer();
protected:
void connect() override;
void mux() override;
void disconnect() override;
const std::string getFormatName();
void getSuitableTimebase(AVRational& vtb, AVRational& atb);
private:
AVFormatContext* _pFormatCtx;
std::vector<Stream::Ptr> _streams;
};
inline bool Muxer::Stream::operator<(const Stream& other)
{
return _timeline < other._timeline;
}
inline int Muxer::Stream::id() const
{
return _pStream->id;
}
inline Core::PacketQueue::Ptr Muxer::Stream::queue()
{
return _pQueue;
}
inline AVRational Muxer::Stream::timebase()
{
return _timebase;
}
inline uint64_t Muxer::Stream::getTimeline()
{
return _timeline;
}
inline void Muxer::Stream::setTimeline(uint64_t t)
{
_timeline = t;
}
inline Core::SerialCodeWatcher& Muxer::Stream::serialCodeWatcher()
{
return _serialCodeWatcher;
}
}
}
#endif

423
video/ffmpeg/Util.cpp Normal file
View file

@ -0,0 +1,423 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Util.h"
#include <Poco/Exception.h>
#include <Poco/Format.h>
#include <array>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* TYPEDEFS
*******************************************************************************
*/
struct CodecIDItem
{
Core::CodecID cid;
AVCodecID avid;
};
struct SampleFormatItem
{
Core::SampleFormat cfmt;
AVSampleFormat avfmt;
};
/*
*******************************************************************************
* LOCAL VARIABLES
*******************************************************************************
*/
static const std::array<CodecIDItem, 8> codecMap = {{
{Core::CodecID::H264, AV_CODEC_ID_H264},
{Core::CodecID::H265, AV_CODEC_ID_H265},
{Core::CodecID::MJPEG, AV_CODEC_ID_MJPEG},
{Core::CodecID::MP3, AV_CODEC_ID_MP3},
{Core::CodecID::AAC, AV_CODEC_ID_AAC},
{Core::CodecID::OPUS, AV_CODEC_ID_OPUS},
{Core::CodecID::G711A, AV_CODEC_ID_PCM_ALAW},
{Core::CodecID::G711U, AV_CODEC_ID_PCM_MULAW},
}};
static const std::array<SampleFormatItem, 12> formatMap = {{
{
Core::SampleFormat::U8,
AV_SAMPLE_FMT_U8,
},
{
Core::SampleFormat::S16,
AV_SAMPLE_FMT_S16,
},
{
Core::SampleFormat::S32,
AV_SAMPLE_FMT_S32,
},
{
Core::SampleFormat::S64,
AV_SAMPLE_FMT_S64,
},
{
Core::SampleFormat::FLT,
AV_SAMPLE_FMT_FLT,
},
{
Core::SampleFormat::DBL,
AV_SAMPLE_FMT_DBL,
},
{
Core::SampleFormat::U8P,
AV_SAMPLE_FMT_U8P,
},
{
Core::SampleFormat::S16P,
AV_SAMPLE_FMT_S16P,
},
{
Core::SampleFormat::S32P,
AV_SAMPLE_FMT_S32P,
},
{
Core::SampleFormat::S64P,
AV_SAMPLE_FMT_S64P,
},
{
Core::SampleFormat::FLTP,
AV_SAMPLE_FMT_FLTP,
},
{
Core::SampleFormat::DBLP,
AV_SAMPLE_FMT_DBLP,
},
}};
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
static Core::CodecID findCodecID(AVCodecID id)
{
for (const auto& m : codecMap)
{
if (id == m.avid)
return m.cid;
}
throw Poco::NotFoundException(
Poco::format("Video::FFmpeg: Can't find %s in codec-map", Util::getCodecIDString(id)));
}
static AVCodecID findCodecID(Core::CodecID id)
{
for (const auto& m : codecMap)
{
if (id == m.cid)
return m.avid;
}
throw Poco::NotFoundException(Poco::format("Video::FFmpeg: Can't find %s in codec-map", Core::codecIDToString(id)));
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
std::string Util::getMediaTypeString(AVMediaType mediaType)
{
return av_get_media_type_string(mediaType);
}
std::string Util::getErrorString(int err)
{
char buf[AV_ERROR_MAX_STRING_SIZE];
return av_make_error_string(buf, AV_ERROR_MAX_STRING_SIZE, err);
}
std::string Util::getCodecIDString(AVCodecID id)
{
const char* name = avcodec_get_name(id);
if (name != nullptr)
return name;
return Poco::format("UNKNOWN(%u)", (uint32_t)id);
}
std::string Util::getSampleFormatString(AVSampleFormat fmt)
{
const char* name = av_get_sample_fmt_name(fmt);
if (name != nullptr)
return name;
return Poco::format("UNKNOWN(%u)", (uint32_t)fmt);
}
const AVCodec* Util::getEncoder(Core::CodecID codec)
{
return avcodec_find_encoder(findCodecID(codec));
}
const AVCodec* Util::getDecoder(Core::CodecID codec)
{
return avcodec_find_decoder(findCodecID(codec));
}
const Core::CodecID Util::getCodecID(AVCodecID id)
{
return findCodecID(id);
}
AVChannelLayout Util::getChannelLayout(Core::ChannelLayout layout)
{
switch (layout)
{
case Core::ChannelLayout::MONO:
case Core::ChannelLayout::L:
case Core::ChannelLayout::R:
return AV_CHANNEL_LAYOUT_MONO;
default:
case Core::ChannelLayout::STEREO:
return AV_CHANNEL_LAYOUT_STEREO;
}
}
Core::ChannelLayout Util::getChannelLayout(AVChannelLayout layout)
{
AVChannelLayout other = AV_CHANNEL_LAYOUT_STEREO;
if (av_channel_layout_compare(&layout, &other) == 0)
return Core::ChannelLayout::STEREO;
return Core::ChannelLayout::MONO;
}
AVSampleFormat Util::getSampleFormat(Core::SampleFormat format)
{
for (const auto& m : formatMap)
{
if (format == m.cfmt)
return m.avfmt;
}
throw Poco::NotFoundException(
Poco::format("Video::FFmpeg: Can't find %s in format-map", Core::sampleFormatName(format)));
}
Core::SampleFormat Util::getSampleFormat(AVSampleFormat format)
{
for (const auto& m : formatMap)
{
if (format == m.avfmt)
return m.cfmt;
}
throw Poco::NotFoundException(
Poco::format("Video::FFmpeg: Can't find %s in format-map", getSampleFormatString(format)));
}
int Util::convertJPG2NV16(std::string srcPath, std::string dstPath)
{
#define TARGET_WIDTH 1920
#define TARGET_HEIGHT 1080
const char* input_filename = srcPath.c_str();
const char* output_filename = dstPath.c_str();
AVFormatContext* fmt_ctx = NULL;
AVCodecContext* codec_ctx = NULL;
const AVCodec* codec = NULL;
AVFrame * frame = NULL, *scaled_frame = NULL;
AVPacket* pkt = NULL;
SwsContext* sws_ctx = NULL;
FILE* outfile = NULL;
int ret = 0;
int video_stream_idx = -1;
if ((ret = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL)) < 0)
{
fprintf(stderr, "Cannot open input file\n");
return -1;
}
if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0)
{
fprintf(stderr, "Cannot find stream information\n");
return -1;
}
for (int i = 0; i < fmt_ctx->nb_streams; i++)
{
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
video_stream_idx = i;
break;
}
}
if (video_stream_idx == -1)
{
fprintf(stderr, "No video stream found\n");
ret = -1;
return -1;
}
AVCodecParameters* codecpar = fmt_ctx->streams[video_stream_idx]->codecpar;
codec = avcodec_find_decoder(codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Unsupported codec\n");
ret = -1;
return -1;
}
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx)
{
fprintf(stderr, "Cannot allocate codec context\n");
ret = -1;
return -1;
}
if ((ret = avcodec_parameters_to_context(codec_ctx, codecpar)) < 0)
{
fprintf(stderr, "Cannot copy codec parameters\n");
return -1;
}
if ((ret = avcodec_open2(codec_ctx, codec, NULL)) < 0)
{
fprintf(stderr, "Cannot open codec\n");
return -1;
}
frame = av_frame_alloc();
if (!frame)
{
fprintf(stderr, "Cannot allocate frame\n");
ret = -1;
return -1;
}
scaled_frame = av_frame_alloc();
if (!scaled_frame)
{
fprintf(stderr, "Cannot allocate scaled frame\n");
ret = -1;
return -1;
}
scaled_frame->width = TARGET_WIDTH;
scaled_frame->height = TARGET_HEIGHT;
scaled_frame->format = AV_PIX_FMT_YUV422P;
if ((ret = av_frame_get_buffer(scaled_frame, 32)) < 0)
{
fprintf(stderr, "Cannot allocate scaled frame buffer\n");
return -1;
}
sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, TARGET_WIDTH, TARGET_HEIGHT,
AV_PIX_FMT_YUV422P, SWS_BILINEAR, NULL, NULL, NULL);
if (!sws_ctx)
{
fprintf(stderr, "Cannot create sws context\n");
ret = -1;
return -1;
}
outfile = fopen(output_filename, "wb");
if (!outfile)
{
fprintf(stderr, "Cannot open output file\n");
ret = -1;
return -1;
}
pkt = av_packet_alloc();
if (!pkt)
{
fprintf(stderr, "Cannot allocate packet\n");
ret = -1;
return -1;
}
while (av_read_frame(fmt_ctx, pkt) >= 0)
{
if (pkt->stream_index == video_stream_idx)
{
ret = avcodec_send_packet(codec_ctx, pkt);
if (ret < 0)
{
fprintf(stderr, "Error sending packet\n");
break;
}
while (ret >= 0)
{
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0)
{
fprintf(stderr, "Error receiving frame\n");
break;
}
sws_scale(sws_ctx, (const uint8_t* const*)frame->data, frame->linesize, 0, frame->height,
scaled_frame->data, scaled_frame->linesize);
int width = scaled_frame->width;
int height = scaled_frame->height;
for (int i = 0; i < height; i++)
{
fwrite(scaled_frame->data[0] + i * scaled_frame->linesize[0], 1, width, outfile);
}
for (int i = 0; i < height; i++)
{
uint8_t* u_line = scaled_frame->data[1] + i * scaled_frame->linesize[1];
uint8_t* v_line = scaled_frame->data[2] + i * scaled_frame->linesize[2];
for (int j = 0; j < width / 2; j++)
{
fwrite(&v_line[j], 1, 1, outfile);
fwrite(&u_line[j], 1, 1, outfile);
}
}
printf("Image decoded and converted: %dx%d -> %dx%d\n", frame->width, frame->height, TARGET_WIDTH,
TARGET_HEIGHT);
}
}
av_packet_unref(pkt);
break;
}
printf("Conversion completed. Output saved to %s\n", output_filename);
if (outfile)
fclose(outfile);
if (sws_ctx)
sws_freeContext(sws_ctx);
if (pkt)
av_packet_free(&pkt);
if (frame)
av_frame_free(&frame);
if (scaled_frame)
av_frame_free(&scaled_frame);
if (codec_ctx)
avcodec_free_context(&codec_ctx);
if (fmt_ctx)
avformat_close_input(&fmt_ctx);
return ret < 0 ? -1 : 0;
}
}
}

Some files were not shown because too many files have changed in this diff Show more