Optimise queues and rendering

This commit is contained in:
Niellune
2025-05-20 23:16:06 +03:00
parent 7f3ea88b21
commit 64f709072f
23 changed files with 585 additions and 476 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
#include <condition_variable>
#include "helper/functions.h"
#include "helper/settings.h"
#include "settings.h"
Connector::Connector(uint16_t videoPadding)
: _videoPadding(videoPadding)
+9 -62
View File
@@ -2,7 +2,7 @@
#include <iostream>
#include "helper/functions.h"
#include "helper/settings.h"
#include "settings.h"
Decoder::Decoder()
{
@@ -13,7 +13,7 @@ Decoder::~Decoder()
stop();
}
void Decoder::start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId)
void Decoder::start(AtomicQueue<Message> *data, VideoBuffer *vb, AVCodecID codecId)
{
if (_active)
stop();
@@ -22,7 +22,6 @@ void Decoder::start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId)
_data = data;
_codecId = codecId;
_active = true;
_running = false;
_thread = std::thread(&Decoder::runner, this);
}
void Decoder::stop()
@@ -133,11 +132,7 @@ void Decoder::runner()
void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPacket *packet, AVFrame *frame)
{
SwsContext *sws = nullptr;
int sws_height = 0;
int sws_width = 0;
uint32_t counter = 0;
_running = true;
std::cout << "Video decoder loop started" << std::endl;
@@ -145,20 +140,13 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack
while (_active)
{
// Get raw data segment from queue
RawEntry segment = _data->wait(_active);
std::unique_ptr<Message> segment = _data->wait(_active);
if (!_active)
{
if (segment.data)
{
free(segment.data);
segment.data = nullptr;
}
continue;
}
uint8_t *data_ptr = segment.data + segment.offset; // Pointer to raw data buffer
int data_size = segment.size; // Size of raw data buffer
uint8_t *data_ptr = segment->data();
int data_size = segment->length();
// Feed raw data into the parser and decoder
while (_active && data_size > 0)
@@ -201,52 +189,11 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack
// Receive decoded frames
while (avcodec_receive_frame(context, frame) == 0 && _active)
{
if (!sws || sws_width != frame->width || sws_height != frame->height)
{
if (sws)
{
sws_freeContext(sws);
sws = nullptr;
}
sws_width = frame->width;
sws_height = frame->height;
sws = sws_getContext(sws_width, sws_height, (AVPixelFormat)frame->format,
_vb->width(), _vb->height(), AV_PIX_FMT_YUV420P,
Settings::scaler, nullptr, nullptr, nullptr);
if (!sws)
{
std::cout << "Error creating sws context" << std::endl;
_active = false;
continue;
}
}
// Write decoded frame into video buffer and get output frame pointer
const AVFrame *out = _vb->writeFrame(counter++);
// Scale frame to output format
sws_scale(sws,
frame->data, frame->linesize,
0, sws_height,
out->data,
out->linesize);
// Commit frame to buffer
_vb->commitFrame();
AVFrame* out = _vb->write(counter++);
av_frame_unref(out);
av_frame_move_ref(out, frame);
_vb->commit();
}
}
if (segment.data)
{
free(segment.data);
segment.data = nullptr;
}
}
_running = false;
if (sws)
{
sws_freeContext(sws);
sws = nullptr;
}
}
+5 -4
View File
@@ -11,7 +11,9 @@ extern "C"
#include <thread>
#include "struct/video_buffer.h"
#include "struct/raw_queue.h"
#include "struct/atomic_queue.h"
#include "struct/message.h"
#include "helper/error.h"
class Decoder
{
@@ -20,7 +22,7 @@ public:
Decoder();
~Decoder();
void start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId);
void start(AtomicQueue<Message> *data, VideoBuffer *vb, AVCodecID codecId);
void stop();
private:
@@ -33,9 +35,8 @@ private:
Error _status;
std::atomic<bool> _active = false;
std::atomic<bool> _running = false;
RawQueue *_data = nullptr;
AtomicQueue<Message> *_data = nullptr;
VideoBuffer *_vb = nullptr;
};
+1 -1
View File
@@ -21,4 +21,4 @@ inline void disable_cout()
std::cout.setstate(std::ios_base::failbit);
}
#endif /* SRC_HELPER_FUNCTIONS */
#endif /* SRC_HELPER_FUNCTIONS */
+3 -3
View File
@@ -1,5 +1,5 @@
#ifndef SRC_PROTOCOL_ISENDER
#define SRC_PROTOCOL_ISENDER
#ifndef SRC_HELPER_IPROTOCOL
#define SRC_HELPER_IPROTOCOL
#include <cstdint>
#include <functional>
@@ -13,4 +13,4 @@ public:
virtual void onStatus(const char* status) = 0;
virtual void onDevice(bool connected) = 0;
};
#endif /* SRC_PROTOCOL_ISENDER */
#endif /* SRC_HELPER_IPROTOCOL */
+3 -3
View File
@@ -1,5 +1,5 @@
#ifndef SRC_UX_UFONT
#define SRC_UX_UFONT
#ifndef SRC_HELPER_UFONT
#define SRC_HELPER_UFONT
#include <SDL2/SDL_ttf.h>
#include <iostream>
@@ -59,4 +59,4 @@ private:
TTF_Font *_font = nullptr;
};
#endif /* SRC_UX_UFONT */
#endif /* SRC_HELPER_UFONT */
+3 -3
View File
@@ -1,5 +1,5 @@
#ifndef SRC_UX_UIMAGE
#define SRC_UX_UIMAGE
#ifndef SRC_HELPER_UIMAGE
#define SRC_HELPER_UIMAGE
#include <SDL2/SDL.h>
#include <iostream>
@@ -49,4 +49,4 @@ private:
SDL_Surface *_surface = nullptr;
};
#endif
#endif /* SRC_HELPER_UIMAGE */
+30 -59
View File
@@ -1,5 +1,8 @@
#include <SDL2/SDL.h> // Include SDL2 library headers for graphics and event handling
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <string>
#include <unistd.h>
#include <iostream>
extern "C"
{
@@ -9,33 +12,21 @@ extern "C"
#include <libavutil/imgutils.h> // FFmpeg utility functions for image handling
}
#include <atomic> // C++ atomic types for thread-safe variables
#include <mutex> // C++ mutex for locking resources
#include <condition_variable> // C++ condition variable for thread synchronization
#include <cmath> // Math functions (not explicitly used here but included)
#include <cstdio> // Standard C I/O functions
#include <vector> // C++ dynamic array container
#include <string> // C++ string type
#include <unistd.h>
#include <iostream>
#include "resource/background.h"
#include "resource/font.h"
#include "helper/settings.h"
#include "helper/functions.h"
#include "ux/ufont.h"
#include "ux/uimage.h"
#include "helper/ufont.h"
#include "helper/uimage.h"
#include "struct/video_buffer.h"
#include "protocol.h"
#include "decoder.h"
#include "pcm_audio.h"
#include "renderer.h"
static const char *title = "Fast Car Play v0.1";
static int width = 0;
static int height = 0;
static const char *title = "Fast Car Play v0.2";
static SDL_Window *window = nullptr;
static SDL_Renderer *renderer = nullptr;
bool active = false;
@@ -43,7 +34,6 @@ bool active = false;
static SDL_Texture *textTexture = nullptr;
static std::string textureText = "";
static SDL_Texture *imgTexture = nullptr;
static SDL_Texture *videoTexture = nullptr;
static bool mouseDown = false;
static bool fullscreen = false;
@@ -196,10 +186,7 @@ void application()
SDL_RenderClear(renderer); // Clear renderer to black
SDL_RenderPresent(renderer); // Present initial blank frame
std::cout << " > Application started" << std::endl;
VideoBuffer videoBuffer;
videoBuffer.allocate(Settings::width, Settings::height).throwError();
Protocol protocol(Settings::sourceWidth, Settings::sourceHeight, Settings::sourceFps, AV_INPUT_BUFFER_PADDING_SIZE);
Decoder decoder;
PcmAudio audio0, audio1, audio2;
@@ -221,8 +208,10 @@ void application()
SDL_RenderPresent(renderer);
std::cout << " > Application loop" << std::endl;
Renderer videoRenderer(renderer);
bool dirty = true;
bool connected = false;
bool videoPrepared = false;
const int activeDelay = 1000 / Settings::fps;
const int inactiveDelay = 1000 / 5; // 5FPS
uint32_t frameDelay = inactiveDelay;
@@ -240,6 +229,7 @@ void application()
DrawImage(image);
SDL_RenderPresent(renderer);
dirty = true;
videoPrepared = false;
frameDelay = connected ? activeDelay : inactiveDelay;
}
@@ -247,18 +237,18 @@ void application()
{
AVFrame *frame = nullptr;
uint32_t frameid = 0;
if (videoBuffer.getLatest(&frame, &frameid) && frameid != latestid)
if (videoBuffer.latest(&frame, &frameid) && frameid != latestid && frame)
{
// Update SDL texture with YUV frame data
SDL_UpdateYUVTexture(videoTexture, nullptr,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (!videoPrepared)
videoPrepared = videoRenderer.prepare(frame, Settings::width, Settings::height, Settings::scaler);
if (videoPrepared && videoRenderer.render(frame))
{
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, videoRenderer.texture, nullptr, nullptr);
SDL_RenderPresent(renderer);
}
latestid = frameid;
videoBuffer.consumeLatest();
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, videoTexture, nullptr, nullptr);
SDL_RenderPresent(renderer);
videoBuffer.consume();
}
}
else
@@ -287,6 +277,7 @@ void application()
}
}
std::cout << " > Application stopping" << std::endl;
SDL_HideWindow(window);
}
int main(int argc, char **argv)
@@ -331,23 +322,13 @@ int main(int argc, char **argv)
return 1;
}
if (Settings::fullscreen)
{
width = displayMode.w;
height = displayMode.h;
}
else
{
width = Settings::width;
height = Settings::height;
}
// Create SDL window centered on screen, 800x600 size
// Create SDL window centered on screen
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
window = SDL_CreateWindow(title,
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
width,
height,
Settings::fullscreen ? displayMode.w : Settings::width,
Settings::fullscreen ? displayMode.h : Settings::height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN);
if (!window)
@@ -362,19 +343,9 @@ int main(int argc, char **argv)
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer)
{
videoTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV,
SDL_TEXTUREACCESS_STREAMING,
Settings::width, Settings::height);
if (videoTexture)
{
application();
SDL_DestroyTexture(videoTexture);
std::cout << " > Application finish" << std::endl;
}
else
{
std::cerr << "[Main] SDL can't create video texture: " << SDL_GetError() << std::endl;
}
std::cout << " > Application started" << std::endl;
application();
std::cout << " > Application finish" << std::endl;
SDL_DestroyRenderer(renderer);
}
else
+30 -59
View File
@@ -11,12 +11,12 @@ PcmAudio::~PcmAudio()
void PcmAudio::setVolume(float vol)
{
if(vol<0)
if (vol < 0)
{
_volume = 0;
return;
}
if(vol>1)
if (vol > 1)
{
_volume = 1;
return;
@@ -24,7 +24,7 @@ void PcmAudio::setVolume(float vol)
_volume = vol;
}
void PcmAudio::start(RawQueue *data)
void PcmAudio::start(AtomicQueue<Message> *data)
{
if (_active)
stop();
@@ -43,6 +43,21 @@ void PcmAudio::stop()
_thread.join();
}
static ChannelConfig configTable[] = {
{8000, 1}, // type = 3
{48000, 2}, // type = 4
{16000, 1}, // type = 5
{24000, 1}, // type = 6
{16000, 2}, // type = 7
};
ChannelConfig PcmAudio::getConfig(int type) const
{
if (type >= 3 && type <= 7)
return configTable[type - 3];
return {44100, 2};
}
void PcmAudio::runner()
{
setThreadName("audio");
@@ -52,63 +67,21 @@ void PcmAudio::runner()
size_t bufferedBytes = 0;
bool unpaused = false;
size_t targetBytes = 0;
int rate = 0;
int channels = 0;
ChannelConfig config = {0, 0};
while (_active)
{
RawEntry segment = _data->wait(_active);
unique_ptr<Message> segment = _data->wait(_active);
if (!_active)
{
if (segment.data)
free(segment.data);
break;
}
int config = 0;
memcpy(&config, segment.data, sizeof(int));
int newRate = 44100;
int newChannels = 2;
switch (config)
{
case 1:
newRate = 44100;
newChannels = 2;
break;
case 2:
newRate = 44100;
newChannels = 2;
break;
case 3:
newRate = 8000;
newChannels = 1;
break;
case 4:
newRate = 48000;
newChannels = 2;
break;
case 5:
newRate = 16000;
newChannels = 1;
break;
case 6:
newRate = 24000;
newChannels = 1;
break;
case 7:
newRate = 16000;
newChannels = 2;
break;
}
ChannelConfig segmentConfig = getConfig(segment->getInt(OFFSET_AUDIO_FORMAT));
// If settings changed, (re)open audio device
if (device == 0 || rate != newRate || channels != newChannels)
if (device == 0 || config != segmentConfig)
{
rate = newRate;
channels = newChannels;
config = segmentConfig;
printf("PCM SETTING %d %d\n", rate, channels);
// Close existing device
if (device != 0)
{
@@ -117,9 +90,9 @@ void PcmAudio::runner()
}
// Configure new spec
SDL_zero(spec);
spec.freq = rate;
spec.freq = config.rate;
spec.format = AUDIO_S16SYS;
spec.channels = static_cast<Uint8>(channels);
spec.channels = config.channels;
spec.samples = 4096;
spec.callback = nullptr;
@@ -127,11 +100,10 @@ void PcmAudio::runner()
if (device == 0)
{
std::cerr << "Failed to open audio: " << SDL_GetError() << std::endl;
free(segment.data);
continue;
}
// Calculate new buffer target: 0.5s
targetBytes = rate * channels * sizeof(int16_t) / 2;
targetBytes = config.rate * config.channels * sizeof(int16_t) / 2;
bufferedBytes = 0;
unpaused = false;
// Start paused
@@ -139,17 +111,16 @@ void PcmAudio::runner()
}
// Apply volume in-place
int16_t *samples = reinterpret_cast<int16_t *>(segment.data + segment.offset);
size_t count = segment.size / sizeof(int16_t);
int16_t *samples = reinterpret_cast<int16_t *>(segment->data());
size_t count = segment->length() / sizeof(int16_t);
for (size_t i = 0; i < count; ++i)
{
samples[i] = static_cast<int16_t>(samples[i] * _volume);
}
// Queue audio
SDL_QueueAudio(device, segment.data + segment.offset, segment.size);
bufferedBytes += segment.size;
free(segment.data);
SDL_QueueAudio(device, segment->data(), segment->length());
bufferedBytes += segment->length();
// Unpause when enough buffered
if (!unpaused && bufferedBytes >= targetBytes)
+22 -6
View File
@@ -7,28 +7,44 @@
#include <SDL2/SDL.h>
#include "struct/raw_queue.h"
#include "struct/atomic_queue.h"
#include "struct/message.h"
#include "helper/error.h"
struct ChannelConfig
{
int rate;
uint8_t channels;
class PcmAudio {
bool operator==(ChannelConfig const &other) const
{
return rate == other.rate && channels == other.channels;
}
bool operator!=(ChannelConfig const &other) const
{
return !(*this == other);
}
};
class PcmAudio
{
public:
PcmAudio();
~PcmAudio();
// Start playing raw PCM data from queue
void start(RawQueue* data);
void start(AtomicQueue<Message> *data);
void stop();
void setVolume(float vol);
private:
ChannelConfig getConfig(int type) const;
void runner();
void loop(SDL_AudioDeviceID device);
RawQueue* _data = nullptr;
int _sampleRate = 0;
int _channels = 0;
AtomicQueue<Message> *_data = nullptr;
float _volume = 1.0f;
+4 -4
View File
@@ -178,7 +178,7 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data)
{
if (length <= 20)
break;
videoData.push(data, 20, length - 20);
videoData.pushDiscard( std::make_unique<Message>(data, length, 20));
dispose = false;
break;
}
@@ -193,19 +193,19 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data)
memcpy(&channel, data + 8, sizeof(int));
if (channel == 0)
{
audioStream0.push(data, 12, length - 12);
audioStream0.pushDiscard(std::make_unique<Message>(data, length, 12));
dispose = false;
break;
}
if (channel == 1)
{
audioStream1.push(data, 12, length - 12);
audioStream1.pushDiscard(std::make_unique<Message>(data, length, 12));
dispose = false;
break;
}
if (channel == 2)
{
audioStream2.push(data, 12, length - 12);
audioStream2.pushDiscard(std::make_unique<Message>(data, length, 12));
dispose = false;
break;
}
+7 -6
View File
@@ -1,9 +1,10 @@
#ifndef SRC_PROTOCOL
#define SRC_PROTOCOL
#include "struct/raw_queue.h"
#include "struct/atomic_queue.h"
#include "struct/message.h"
#include "helper/iprotocol.h"
#include "helper/settings.h"
#include "settings.h"
#include "connector.h"
#define MAGIC 0x55aa55aa
@@ -40,10 +41,10 @@ public:
void sendMove(float dx, float dy);
Connector connector;
RawQueue videoData;
RawQueue audioStream0;
RawQueue audioStream1;
RawQueue audioStream2;
AtomicQueue<Message> videoData;
AtomicQueue<Message> audioStream0;
AtomicQueue<Message> audioStream1;
AtomicQueue<Message> audioStream2;
bool phoneConnected;
private:
+183
View File
@@ -0,0 +1,183 @@
#include "renderer.h"
#include "helper/error.h"
#include <iostream>
const Renderer::FormatMapping Renderer::_mapping[] = {
{AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24, &Renderer::rgb, "RGB24"},
{AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUV420P"},
{AV_PIX_FMT_YUVJ420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUVJ420P"},
{AV_PIX_FMT_NV12, SDL_PIXELFORMAT_NV12, &Renderer::nv, "NV12"}};
Renderer::Renderer(SDL_Renderer *renderer)
: texture(nullptr),
textureWidth(0),
textureHeight(0),
_renderer(renderer),
_render(nullptr),
_sws(nullptr),
_swsWidth(0),
_swsHeight(0),
_frame(nullptr)
{
}
Renderer::~Renderer()
{
if (texture)
{
SDL_DestroyTexture(texture);
texture = nullptr;
}
if (_sws)
{
sws_freeContext(_sws);
_sws = nullptr;
}
if (_frame)
{
av_frame_free(&_frame);
_frame = nullptr;
}
}
bool Renderer::render(AVFrame *frame)
{
if (_render == nullptr)
return false;
(this->*_render)(frame);
return true;
}
bool Renderer::prepareTexture(uint32_t format, int width, int height)
{
if (texture)
{
if (textureWidth == width && textureHeight == height)
return true;
SDL_DestroyTexture(texture);
texture = nullptr;
}
texture = SDL_CreateTexture(_renderer, format,
SDL_TEXTUREACCESS_STREAMING,
width, height);
if (!texture)
{
std::cerr << "[Render] SDL can't create video texture: " << SDL_GetError() << std::endl;
return false;
}
return true;
}
bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler)
{
if (frame->width == targetWidth && frame->height == targetHeight)
{
AVPixelFormat fmt = static_cast<AVPixelFormat>(frame->format);
for (const FormatMapping &mapping : _mapping)
{
if (mapping.avFormat == fmt)
{
if (prepareTexture(mapping.sdlFormat, targetWidth, targetHeight))
{
std::cout << "[Render] Direct rendering " << mapping.name << std::endl;
_render = mapping.function;
return true;
}
}
}
}
else
{
std::cout << "[Render] Scaling required from " << frame->width << "x" << frame->height << " to " << targetWidth << "x" << targetHeight << std::endl;
}
if (!prepareTexture(SDL_PIXELFORMAT_IYUV, targetWidth, targetHeight))
return false;
if (!_sws || _swsWidth != frame->width || _swsHeight != frame->height)
{
if (_sws)
{
sws_freeContext(_sws);
_sws = nullptr;
}
_sws = sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format,
targetWidth, targetHeight, AV_PIX_FMT_YUV420P,
scaler, nullptr, nullptr, nullptr);
if (!_sws)
{
std::cerr << "[Render] Can't create sws context" << std::endl;
return false;
}
_swsWidth = frame->width;
_swsHeight = frame->height;
}
if (!_frame)
{
_frame = av_frame_alloc();
if (!_frame)
{
std::cerr << "[Render] Can't allocate AVFrame" << std::endl;
return false;
}
_frame->format = AV_PIX_FMT_YUV420P;
_frame->width = targetWidth;
_frame->height = targetHeight;
// Allocate data buffer with 32 byte allingment
int avRes = av_frame_get_buffer(_frame, 32);
if (avRes != 0)
{
std::cerr << "[Render] Can't allocate AVFrame buffer: " << Error::avErrorText(avRes) << std::endl;
return false;
}
}
std::cout << "[Render] Scaling rendering source format " << frame->format << std::endl;
_render = &Renderer::scale;
return true;
}
void Renderer::rgb(AVFrame *frame)
{
SDL_UpdateTexture(
texture,
nullptr,
frame->data[0], frame->linesize[0]);
}
void Renderer::nv(AVFrame *frame)
{
SDL_UpdateNVTexture(
texture,
nullptr,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1]);
}
void Renderer::yuv(AVFrame *frame)
{
SDL_UpdateYUVTexture(
texture,
nullptr,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
}
void Renderer::scale(AVFrame *frame)
{
// Scale frame to output format
sws_scale(_sws,
frame->data, frame->linesize,
0, _swsHeight,
_frame->data,
_frame->linesize);
// Update SDL texture with YUV frame data
SDL_UpdateYUVTexture(texture, nullptr,
_frame->data[0], _frame->linesize[0],
_frame->data[1], _frame->linesize[1],
_frame->data[2], _frame->linesize[2]);
}
+53
View File
@@ -0,0 +1,53 @@
#ifndef SRC_RENDERER
#define SRC_RENDERER
extern "C"
{
#include <libavformat/avformat.h> // FFmpeg library for multimedia container format handling
#include <libswscale/swscale.h> // FFmpeg library for image scaling and pixel format conversion
}
#include <SDL2/SDL.h>
#include <string>
class Renderer
{
public:
Renderer(SDL_Renderer *renderer);
~Renderer();
bool prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler);
bool render(AVFrame *frame);
SDL_Texture *texture;
int textureWidth;
int textureHeight;
private:
using DrawFuncType = void (Renderer::*)(AVFrame *);
struct FormatMapping
{
AVPixelFormat avFormat;
SDL_PixelFormatEnum sdlFormat;
DrawFuncType function;
std::string name;
};
bool prepareTexture(uint32_t format, int width, int height);
void rgb(AVFrame *frame);
void nv(AVFrame *frame);
void yuv(AVFrame *frame);
void scale(AVFrame *frame);
SDL_Renderer *_renderer;
DrawFuncType _render;
SwsContext *_sws;
int _swsWidth;
int _swsHeight;
AVFrame *_frame;
static const FormatMapping _mapping[];
};
#endif /* SRC_RENDERER */
+6 -6
View File
@@ -1,7 +1,7 @@
#ifndef SRC_HELPER_SETTINGS
#define SRC_HELPER_SETTINGS
#ifndef SRC_SETTINGS
#define SRC_SETTINGS
#include "settings_base.h"
#include "helper/settings_base.h"
// The singleton “Settings” namespace
class Settings
@@ -9,14 +9,14 @@ class Settings
public:
static inline Setting<int> vendorid{"vendor-id", 4884};
static inline Setting<int> productid{"product-id ", 5408};
static inline Setting<bool> fullscreen{"fullscreen", false};
static inline Setting<bool> fullscreen{"fullscreen", true};
static inline Setting<int> width{"width", 720};
static inline Setting<int> height{"height", 576};
static inline Setting<int> fps{"fps", 60};
static inline Setting<int> sourceWidth{"source-width", 720};
static inline Setting<int> sourceHeight{"source-height", 576};
static inline Setting<int> sourceFps{"source-fps", 30};
static inline Setting<bool> logging{"logging", true};
static inline Setting<bool> logging{"logging", false};
static inline Setting<int> scaler{"scaler", 2};
static inline Setting<int> queue{"queue-size", 32};
static inline Setting<int> fontSize{"font-size", 30};
@@ -28,4 +28,4 @@ private:
static void trim(std::string &s);
};
#endif /* SRC_HELPER_SETTINGS */
#endif /* SRC_SETTINGS */
+105
View File
@@ -0,0 +1,105 @@
#ifndef SRC_STRUCT_ATOMIC_QUEUE
#define SRC_STRUCT_ATOMIC_QUEUE
#include <cstdint>
#include <atomic>
#include <memory>
#include <condition_variable>
#include <mutex>
using namespace std;
template <typename T>
class AtomicQueue
{
public:
AtomicQueue(uint16_t size)
: _size(size), _data(new unique_ptr<T>[size]), _first(0), _last(0), _count(0)
{
}
AtomicQueue(const AtomicQueue &) = delete;
AtomicQueue &operator=(const AtomicQueue &) = delete;
~AtomicQueue() = default;
bool pushDiscard(unique_ptr<T> obj)
{
if (_count == _size)
return false;
_first = (_first + 1) % _size;
_data[_first] = std::move(obj);
++_count;
_lock.notify_one();
return true;
}
bool pushReplace(unique_ptr<T> obj)
{
if (_count == _size)
{
_data[_first] = std::move(obj);
return false;
}
_first = (_first + 1) % _size;
_data[_first] = std::move(obj);
++_count;
_lock.notify_one();
return true;
}
unique_ptr<T> pop()
{
if (_count == 0)
return nullptr;
_last = (_last + 1) % _size;
auto item = std::move(_data[_last]);
--_count;
return item;
}
unique_ptr<T> wait(atomic<bool> &waitFlag)
{
unique_lock<std::mutex> lock(_mtx);
_lock.wait(lock, [&]
{ return _count > 0 || !waitFlag; });
if (!waitFlag)
return nullptr;
_last = (_last + 1) % _size;
auto item = std::move(_data[_last]);
--_count;
return item;
}
void clear()
{
_data = std::make_unique<std::unique_ptr<T>[]>(_size);
_first = 0;
_last = 0;
_count = 0;
}
void notify()
{
_lock.notify_all();
}
uint16_t count() { return _count; }
private:
uint16_t _size;
unique_ptr<unique_ptr<T>[]> _data;
uint16_t _first;
uint16_t _last;
atomic<uint16_t> _count;
mutex _mtx;
condition_variable _lock;
};
#endif /* SRC_STRUCT_ATOMIC_QUEUE */
+43
View File
@@ -0,0 +1,43 @@
#ifndef SRC_STRUCT_MESSAGE
#define SRC_STRUCT_MESSAGE
#include <cstdint>
#include <cstring>
#include <memory>
#define OFFSET_AUDIO_FORMAT 0
class Message
{
public:
Message(uint8_t *data, uint32_t data_length, uint32_t offset) : _data(data), _length(data_length), _offset(offset)
{
}
~Message()
{
if (_data)
{
free(_data);
_data = nullptr;
}
}
int getInt(uint32_t offset) const
{
int result = 0;
if (_length - sizeof(int) >= offset)
memcpy(&result, _data + offset, sizeof(int));
return result;
}
uint8_t *data() const { return _data + _offset; }
uint32_t length() const { return _length - _offset; }
private:
uint8_t *_data;
uint32_t _length;
uint32_t _offset;
};
#endif /* SRC_STRUCT_MESSAGE */
-69
View File
@@ -1,69 +0,0 @@
#include "raw_queue.h"
RawQueue::RawQueue(uint16_t capacity)
: _buffer(capacity), _head(0), _tail(0), _size(0), _capacity(capacity)
{
}
RawQueue::~RawQueue()
{
clear();
}
bool RawQueue::push(uint8_t *data, int offset, int size)
{
std::lock_guard<std::mutex> lock(_mutex);
if (_size == _buffer.size())
{
free(data);
return false; // queue full
}
_buffer[_tail] = RawEntry{data, offset, size};
_tail = (_tail + 1) % _capacity;
_size++;
_condition.notify_one();
return true;
}
RawEntry RawQueue::wait(const std::atomic<bool> &reading)
{
std::unique_lock<std::mutex> lock(_mutex);
_condition.wait(lock, [&]
{ return !reading.load() || _size > 0; });
if (!reading || _size == 0)
return RawEntry{nullptr, 0, 0};
RawEntry entry = _buffer[_head];
_head = (_head + 1) % _capacity;
_size--;
return entry;
}
void RawQueue::clear()
{
std::lock_guard<std::mutex> lock(_mutex);
// Free any remaining buffers
while (_size > 0)
{
RawEntry &e = _buffer[_head];
if (e.data)
{
free(e.data);
e.data = nullptr;
}
_head = (_head + 1) % _capacity;
_size--;
}
// Reset indices
_head = _tail = 0;
}
void RawQueue::notify()
{
_condition.notify_all();
}
-46
View File
@@ -1,46 +0,0 @@
#ifndef SRC_RAW_QUEUE
#define SRC_RAW_QUEUE
#include <cstdint>
#include <vector>
#include <atomic>
#include <mutex>
#include <condition_variable>
struct RawEntry
{
uint8_t *data;
int offset;
int size;
};
// Single entry: raw buffer pointer + metadata
class RawQueue
{
public:
RawQueue(uint16_t capacity = 256);
~RawQueue();
// Non-blocking push: returns false if full
bool push(uint8_t *data, int offset, int size);
// Blocks until an entry is available or reader_active == false
RawEntry wait(const std::atomic<bool> &reading);
// Clears the queue and frees any pending buffers
void clear();
// Unlock queus
void notify();
private:
std::vector<RawEntry> _buffer;
uint16_t _head;
uint16_t _tail;
uint16_t _size;
uint16_t _capacity;
std::mutex _mutex;
std::condition_variable _condition;
};
#endif
-111
View File
@@ -1,111 +0,0 @@
#include "video_buffer.h"
extern "C"
{
#include <libavutil/imgutils.h>
}
#include <stdexcept>
#include <string>
#include "error.h"
VideoBuffer::VideoBuffer()
: _width(0), _height(0)
{
}
// Allocate two YUV420P frames for double buffering and initialize to black
Error VideoBuffer::allocate(uint16_t width, uint16_t height)
{
_width = width;
_height = height;
deallocate();
reset();
Error e;
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
{
// Allocate AVFrame
_frames[i] = av_frame_alloc();
if (e.null(_frames[i], "Failed to allocate AVFrame"))
break;
_frames[i]->format = AV_PIX_FMT_YUV420P;
_frames[i]->width = width;
_frames[i]->height = height;
// Allocate data buffer with 32 byte allingment
if (e.avFail(av_frame_get_buffer(_frames[i], 32), "Failed to allocate AVFrame buffer"))
break;
// Set Y plane to black (0)
memset(_frames[i]->data[0], 0, _frames[i]->linesize[0] * height);
// Set U plane to 128 (neutral)
memset(_frames[i]->data[1], 128, _frames[i]->linesize[1] * (height / 2));
// Set V plane to 128 (neutral)
memset(_frames[i]->data[2], 128, _frames[i]->linesize[2] * (height / 2));
}
return e;
}
VideoBuffer::~VideoBuffer()
{
deallocate();
}
void VideoBuffer::deallocate()
{
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
{
if (_frames[i])
{
// Free the frame itself
av_frame_free(&_frames[i]);
// Clear
_frames[i] = nullptr;
}
}
}
bool VideoBuffer::getLatest(AVFrame **frame, uint32_t *id)
{
_reading.store(_latest.load());
int index = _reading.load();
if (index < 0)
return false;
*frame = _frames[index];
*id = _ids[index];
return true;
}
void VideoBuffer::consumeLatest()
{
_reading.store(-1);
}
const AVFrame *VideoBuffer::writeFrame(uint32_t id)
{
int index = _writing.load();
while (index == _reading.load() || index == _latest.load())
{
index = (index + 1) % BUFFER_VIDEO_FRAMES;
}
_writing.store(index);
_ids[index] = id;
return _frames[index];
}
void VideoBuffer::commitFrame()
{
_latest.store(_writing.load());
}
void VideoBuffer::reset()
{
_writing.store(0);
_reading.store(-1);
_latest.store(-1);
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; i++)
{
_ids[i] = 0;
}
}
+64 -20
View File
@@ -1,5 +1,5 @@
#ifndef SRC_VIDEO_BUFFER
#define SRC_VIDEO_BUFFER
#ifndef SRC_STRUCT_VIDEO_BUFFER
#define SRC_STRUCT_VIDEO_BUFFER
extern "C"
{
@@ -7,31 +7,75 @@ extern "C"
}
#include <atomic>
#include "helper/error.h"
#include <stdexcept>
#define BUFFER_VIDEO_FRAMES 3
class VideoBuffer
{
public:
VideoBuffer();
~VideoBuffer();
Error allocate(uint16_t width, uint16_t height);
uint16_t width() const { return _width; };
uint16_t height() const { return _height; };
void reset();
bool getLatest(AVFrame **frame, uint32_t *id);
void consumeLatest();
const AVFrame *writeFrame(uint32_t id);
void commitFrame();
VideoBuffer()
{
_writing.store(0);
_reading.store(-1);
_latest.store(-1);
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
{
_ids[i] = 0;
_frames[i] = av_frame_alloc();
if (!_frames[i])
{
throw std::runtime_error("Failed to allocate AVFrame");
}
}
}
~VideoBuffer()
{
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
{
if (_frames[i])
{
av_frame_free(&_frames[i]);
_frames[i] = nullptr;
}
}
}
bool latest(AVFrame **frame, uint32_t *id)
{
_reading.store(_latest.load());
int index = _reading.load();
if (index < 0)
return false;
*frame = _frames[index];
*id = _ids[index];
return true;
}
void consume()
{
_reading.store(-1);
}
AVFrame *write(uint32_t id)
{
int index = _writing.load();
while (index == _reading.load() || index == _latest.load())
{
index = (index + 1) % BUFFER_VIDEO_FRAMES;
}
_writing.store(index);
_ids[index] = id;
return _frames[index];
}
void commit()
{
_latest.store(_writing.load());
}
private:
void deallocate();
uint16_t _width;
uint16_t _height;
std::atomic<int8_t> _latest;
std::atomic<int8_t> _reading;
std::atomic<int8_t> _writing;
@@ -39,4 +83,4 @@ private:
uint32_t _ids[BUFFER_VIDEO_FRAMES];
};
#endif /* SRC_VIDEO_BUFFER */
#endif /* SRC_STRUCT_VIDEO_BUFFER */