mirror of
https://github.com/niellun/FastCarPlay.git
synced 2026-09-23 02:10:42 +02:00
Optimise queues and rendering
This commit is contained in:
+13
-13
@@ -1,20 +1,20 @@
|
|||||||
# Dongle configuration
|
# Dongle configuration
|
||||||
# Note that vendor id and product id are in decimals, not hex here
|
# Note that vendor id and product id are in decimals, not hex here
|
||||||
vendor-id = 4884 # 0x1314
|
#vendor-id = 4884 # 0x1314
|
||||||
product-id = 5408 # 0x1520
|
#product-id = 5408 # 0x1520
|
||||||
|
|
||||||
# Application starts in full screen
|
# Application starts in full screen
|
||||||
fullscreen = true
|
#fullscreen = true
|
||||||
|
|
||||||
# Application drawing settings widthxheight
|
# Application drawing settings widthxheight
|
||||||
width = 720
|
#width = 720
|
||||||
height = 576
|
#height = 576
|
||||||
fps = 30
|
#fps = 60
|
||||||
|
|
||||||
# Requested image from phone
|
# Requested image from phone
|
||||||
source-width = 1080
|
#source-width = 720
|
||||||
source-height = 864
|
#source-height = 576
|
||||||
source-fps = 30
|
#source-fps = 30
|
||||||
|
|
||||||
# Scaler algorithm if application drawing is differen from source image
|
# Scaler algorithm if application drawing is differen from source image
|
||||||
# It's recommended to keep application and source values same cause scaling
|
# It's recommended to keep application and source values same cause scaling
|
||||||
@@ -31,13 +31,13 @@ source-fps = 30
|
|||||||
# SWS_SINC 256
|
# SWS_SINC 256
|
||||||
# SWS_LANCZOS 512
|
# SWS_LANCZOS 512
|
||||||
# SWS_SPLINE 1024
|
# SWS_SPLINE 1024
|
||||||
scaler = 1024
|
#scaler = 2
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging = false
|
logging = true
|
||||||
|
|
||||||
# Size of video and audio buffers. Increase if you see artifacts
|
# Size of video and audio buffers. Increase if you see artifacts
|
||||||
queue-size = 32
|
#queue-size = 32
|
||||||
|
|
||||||
# Font size for messgaes on screen
|
# Font size for messgaes on screen
|
||||||
font-size = 30
|
#font-size = 30
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
|
||||||
#include "helper/functions.h"
|
#include "helper/functions.h"
|
||||||
#include "helper/settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
Connector::Connector(uint16_t videoPadding)
|
Connector::Connector(uint16_t videoPadding)
|
||||||
: _videoPadding(videoPadding)
|
: _videoPadding(videoPadding)
|
||||||
|
|||||||
+9
-62
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "helper/functions.h"
|
#include "helper/functions.h"
|
||||||
#include "helper/settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
Decoder::Decoder()
|
Decoder::Decoder()
|
||||||
{
|
{
|
||||||
@@ -13,7 +13,7 @@ Decoder::~Decoder()
|
|||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Decoder::start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId)
|
void Decoder::start(AtomicQueue<Message> *data, VideoBuffer *vb, AVCodecID codecId)
|
||||||
{
|
{
|
||||||
if (_active)
|
if (_active)
|
||||||
stop();
|
stop();
|
||||||
@@ -22,7 +22,6 @@ void Decoder::start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId)
|
|||||||
_data = data;
|
_data = data;
|
||||||
_codecId = codecId;
|
_codecId = codecId;
|
||||||
_active = true;
|
_active = true;
|
||||||
_running = false;
|
|
||||||
_thread = std::thread(&Decoder::runner, this);
|
_thread = std::thread(&Decoder::runner, this);
|
||||||
}
|
}
|
||||||
void Decoder::stop()
|
void Decoder::stop()
|
||||||
@@ -133,11 +132,7 @@ void Decoder::runner()
|
|||||||
|
|
||||||
void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPacket *packet, AVFrame *frame)
|
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;
|
uint32_t counter = 0;
|
||||||
_running = true;
|
|
||||||
|
|
||||||
std::cout << "Video decoder loop started" << std::endl;
|
std::cout << "Video decoder loop started" << std::endl;
|
||||||
|
|
||||||
@@ -145,20 +140,13 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack
|
|||||||
while (_active)
|
while (_active)
|
||||||
{
|
{
|
||||||
// Get raw data segment from queue
|
// Get raw data segment from queue
|
||||||
RawEntry segment = _data->wait(_active);
|
std::unique_ptr<Message> segment = _data->wait(_active);
|
||||||
|
|
||||||
if (!_active)
|
if (!_active)
|
||||||
{
|
|
||||||
if (segment.data)
|
|
||||||
{
|
|
||||||
free(segment.data);
|
|
||||||
segment.data = nullptr;
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t *data_ptr = segment.data + segment.offset; // Pointer to raw data buffer
|
uint8_t *data_ptr = segment->data();
|
||||||
int data_size = segment.size; // Size of raw data buffer
|
int data_size = segment->length();
|
||||||
|
|
||||||
// Feed raw data into the parser and decoder
|
// Feed raw data into the parser and decoder
|
||||||
while (_active && data_size > 0)
|
while (_active && data_size > 0)
|
||||||
@@ -201,52 +189,11 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack
|
|||||||
// Receive decoded frames
|
// Receive decoded frames
|
||||||
while (avcodec_receive_frame(context, frame) == 0 && _active)
|
while (avcodec_receive_frame(context, frame) == 0 && _active)
|
||||||
{
|
{
|
||||||
if (!sws || sws_width != frame->width || sws_height != frame->height)
|
AVFrame* out = _vb->write(counter++);
|
||||||
{
|
av_frame_unref(out);
|
||||||
if (sws)
|
av_frame_move_ref(out, frame);
|
||||||
{
|
_vb->commit();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (segment.data)
|
|
||||||
{
|
|
||||||
free(segment.data);
|
|
||||||
segment.data = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
if (sws)
|
|
||||||
{
|
|
||||||
sws_freeContext(sws);
|
|
||||||
sws = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -11,7 +11,9 @@ extern "C"
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "struct/video_buffer.h"
|
#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
|
class Decoder
|
||||||
{
|
{
|
||||||
@@ -20,7 +22,7 @@ public:
|
|||||||
Decoder();
|
Decoder();
|
||||||
~Decoder();
|
~Decoder();
|
||||||
|
|
||||||
void start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId);
|
void start(AtomicQueue<Message> *data, VideoBuffer *vb, AVCodecID codecId);
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -33,9 +35,8 @@ private:
|
|||||||
Error _status;
|
Error _status;
|
||||||
|
|
||||||
std::atomic<bool> _active = false;
|
std::atomic<bool> _active = false;
|
||||||
std::atomic<bool> _running = false;
|
|
||||||
|
|
||||||
RawQueue *_data = nullptr;
|
AtomicQueue<Message> *_data = nullptr;
|
||||||
VideoBuffer *_vb = nullptr;
|
VideoBuffer *_vb = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ inline void disable_cout()
|
|||||||
std::cout.setstate(std::ios_base::failbit);
|
std::cout.setstate(std::ios_base::failbit);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* SRC_HELPER_FUNCTIONS */
|
#endif /* SRC_HELPER_FUNCTIONS */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SRC_PROTOCOL_ISENDER
|
#ifndef SRC_HELPER_IPROTOCOL
|
||||||
#define SRC_PROTOCOL_ISENDER
|
#define SRC_HELPER_IPROTOCOL
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -13,4 +13,4 @@ public:
|
|||||||
virtual void onStatus(const char* status) = 0;
|
virtual void onStatus(const char* status) = 0;
|
||||||
virtual void onDevice(bool connected) = 0;
|
virtual void onDevice(bool connected) = 0;
|
||||||
};
|
};
|
||||||
#endif /* SRC_PROTOCOL_ISENDER */
|
#endif /* SRC_HELPER_IPROTOCOL */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SRC_UX_UFONT
|
#ifndef SRC_HELPER_UFONT
|
||||||
#define SRC_UX_UFONT
|
#define SRC_HELPER_UFONT
|
||||||
|
|
||||||
#include <SDL2/SDL_ttf.h>
|
#include <SDL2/SDL_ttf.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -59,4 +59,4 @@ private:
|
|||||||
TTF_Font *_font = nullptr;
|
TTF_Font *_font = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* SRC_UX_UFONT */
|
#endif /* SRC_HELPER_UFONT */
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SRC_UX_UIMAGE
|
#ifndef SRC_HELPER_UIMAGE
|
||||||
#define SRC_UX_UIMAGE
|
#define SRC_HELPER_UIMAGE
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -49,4 +49,4 @@ private:
|
|||||||
SDL_Surface *_surface = nullptr;
|
SDL_Surface *_surface = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif /* SRC_HELPER_UIMAGE */
|
||||||
+30
-59
@@ -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 <SDL2/SDL_ttf.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
@@ -9,33 +12,21 @@ extern "C"
|
|||||||
#include <libavutil/imgutils.h> // FFmpeg utility functions for image handling
|
#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/background.h"
|
||||||
#include "resource/font.h"
|
#include "resource/font.h"
|
||||||
|
|
||||||
#include "helper/settings.h"
|
|
||||||
#include "helper/functions.h"
|
#include "helper/functions.h"
|
||||||
|
#include "helper/ufont.h"
|
||||||
#include "ux/ufont.h"
|
#include "helper/uimage.h"
|
||||||
#include "ux/uimage.h"
|
|
||||||
|
|
||||||
#include "struct/video_buffer.h"
|
#include "struct/video_buffer.h"
|
||||||
|
|
||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "pcm_audio.h"
|
#include "pcm_audio.h"
|
||||||
|
#include "renderer.h"
|
||||||
|
|
||||||
static const char *title = "Fast Car Play v0.1";
|
static const char *title = "Fast Car Play v0.2";
|
||||||
static int width = 0;
|
|
||||||
static int height = 0;
|
|
||||||
static SDL_Window *window = nullptr;
|
static SDL_Window *window = nullptr;
|
||||||
static SDL_Renderer *renderer = nullptr;
|
static SDL_Renderer *renderer = nullptr;
|
||||||
bool active = false;
|
bool active = false;
|
||||||
@@ -43,7 +34,6 @@ bool active = false;
|
|||||||
static SDL_Texture *textTexture = nullptr;
|
static SDL_Texture *textTexture = nullptr;
|
||||||
static std::string textureText = "";
|
static std::string textureText = "";
|
||||||
static SDL_Texture *imgTexture = nullptr;
|
static SDL_Texture *imgTexture = nullptr;
|
||||||
static SDL_Texture *videoTexture = nullptr;
|
|
||||||
|
|
||||||
static bool mouseDown = false;
|
static bool mouseDown = false;
|
||||||
static bool fullscreen = false;
|
static bool fullscreen = false;
|
||||||
@@ -196,10 +186,7 @@ void application()
|
|||||||
SDL_RenderClear(renderer); // Clear renderer to black
|
SDL_RenderClear(renderer); // Clear renderer to black
|
||||||
SDL_RenderPresent(renderer); // Present initial blank frame
|
SDL_RenderPresent(renderer); // Present initial blank frame
|
||||||
|
|
||||||
std::cout << " > Application started" << std::endl;
|
|
||||||
|
|
||||||
VideoBuffer videoBuffer;
|
VideoBuffer videoBuffer;
|
||||||
videoBuffer.allocate(Settings::width, Settings::height).throwError();
|
|
||||||
Protocol protocol(Settings::sourceWidth, Settings::sourceHeight, Settings::sourceFps, AV_INPUT_BUFFER_PADDING_SIZE);
|
Protocol protocol(Settings::sourceWidth, Settings::sourceHeight, Settings::sourceFps, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||||
Decoder decoder;
|
Decoder decoder;
|
||||||
PcmAudio audio0, audio1, audio2;
|
PcmAudio audio0, audio1, audio2;
|
||||||
@@ -221,8 +208,10 @@ void application()
|
|||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
|
|
||||||
std::cout << " > Application loop" << std::endl;
|
std::cout << " > Application loop" << std::endl;
|
||||||
|
Renderer videoRenderer(renderer);
|
||||||
bool dirty = true;
|
bool dirty = true;
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
|
bool videoPrepared = false;
|
||||||
const int activeDelay = 1000 / Settings::fps;
|
const int activeDelay = 1000 / Settings::fps;
|
||||||
const int inactiveDelay = 1000 / 5; // 5FPS
|
const int inactiveDelay = 1000 / 5; // 5FPS
|
||||||
uint32_t frameDelay = inactiveDelay;
|
uint32_t frameDelay = inactiveDelay;
|
||||||
@@ -240,6 +229,7 @@ void application()
|
|||||||
DrawImage(image);
|
DrawImage(image);
|
||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
videoPrepared = false;
|
||||||
frameDelay = connected ? activeDelay : inactiveDelay;
|
frameDelay = connected ? activeDelay : inactiveDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,18 +237,18 @@ void application()
|
|||||||
{
|
{
|
||||||
AVFrame *frame = nullptr;
|
AVFrame *frame = nullptr;
|
||||||
uint32_t frameid = 0;
|
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
|
if (!videoPrepared)
|
||||||
SDL_UpdateYUVTexture(videoTexture, nullptr,
|
videoPrepared = videoRenderer.prepare(frame, Settings::width, Settings::height, Settings::scaler);
|
||||||
frame->data[0], frame->linesize[0],
|
if (videoPrepared && videoRenderer.render(frame))
|
||||||
frame->data[1], frame->linesize[1],
|
{
|
||||||
frame->data[2], frame->linesize[2]);
|
SDL_RenderClear(renderer);
|
||||||
|
SDL_RenderCopy(renderer, videoRenderer.texture, nullptr, nullptr);
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
}
|
||||||
latestid = frameid;
|
latestid = frameid;
|
||||||
videoBuffer.consumeLatest();
|
videoBuffer.consume();
|
||||||
SDL_RenderClear(renderer);
|
|
||||||
SDL_RenderCopy(renderer, videoTexture, nullptr, nullptr);
|
|
||||||
SDL_RenderPresent(renderer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -287,6 +277,7 @@ void application()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::cout << " > Application stopping" << std::endl;
|
std::cout << " > Application stopping" << std::endl;
|
||||||
|
SDL_HideWindow(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
@@ -331,23 +322,13 @@ int main(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings::fullscreen)
|
// Create SDL window centered on screen
|
||||||
{
|
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
|
||||||
width = displayMode.w;
|
|
||||||
height = displayMode.h;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
width = Settings::width;
|
|
||||||
height = Settings::height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create SDL window centered on screen, 800x600 size
|
|
||||||
window = SDL_CreateWindow(title,
|
window = SDL_CreateWindow(title,
|
||||||
SDL_WINDOWPOS_CENTERED,
|
SDL_WINDOWPOS_CENTERED,
|
||||||
SDL_WINDOWPOS_CENTERED,
|
SDL_WINDOWPOS_CENTERED,
|
||||||
width,
|
Settings::fullscreen ? displayMode.w : Settings::width,
|
||||||
height,
|
Settings::fullscreen ? displayMode.h : Settings::height,
|
||||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN);
|
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN);
|
||||||
|
|
||||||
if (!window)
|
if (!window)
|
||||||
@@ -362,19 +343,9 @@ int main(int argc, char **argv)
|
|||||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
if (renderer)
|
if (renderer)
|
||||||
{
|
{
|
||||||
videoTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV,
|
std::cout << " > Application started" << std::endl;
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
application();
|
||||||
Settings::width, Settings::height);
|
std::cout << " > Application finish" << std::endl;
|
||||||
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;
|
|
||||||
}
|
|
||||||
SDL_DestroyRenderer(renderer);
|
SDL_DestroyRenderer(renderer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
+30
-59
@@ -11,12 +11,12 @@ PcmAudio::~PcmAudio()
|
|||||||
|
|
||||||
void PcmAudio::setVolume(float vol)
|
void PcmAudio::setVolume(float vol)
|
||||||
{
|
{
|
||||||
if(vol<0)
|
if (vol < 0)
|
||||||
{
|
{
|
||||||
_volume = 0;
|
_volume = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(vol>1)
|
if (vol > 1)
|
||||||
{
|
{
|
||||||
_volume = 1;
|
_volume = 1;
|
||||||
return;
|
return;
|
||||||
@@ -24,7 +24,7 @@ void PcmAudio::setVolume(float vol)
|
|||||||
_volume = vol;
|
_volume = vol;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PcmAudio::start(RawQueue *data)
|
void PcmAudio::start(AtomicQueue<Message> *data)
|
||||||
{
|
{
|
||||||
if (_active)
|
if (_active)
|
||||||
stop();
|
stop();
|
||||||
@@ -43,6 +43,21 @@ void PcmAudio::stop()
|
|||||||
_thread.join();
|
_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()
|
void PcmAudio::runner()
|
||||||
{
|
{
|
||||||
setThreadName("audio");
|
setThreadName("audio");
|
||||||
@@ -52,63 +67,21 @@ void PcmAudio::runner()
|
|||||||
size_t bufferedBytes = 0;
|
size_t bufferedBytes = 0;
|
||||||
bool unpaused = false;
|
bool unpaused = false;
|
||||||
size_t targetBytes = 0;
|
size_t targetBytes = 0;
|
||||||
int rate = 0;
|
ChannelConfig config = {0, 0};
|
||||||
int channels = 0;
|
|
||||||
|
|
||||||
while (_active)
|
while (_active)
|
||||||
{
|
{
|
||||||
RawEntry segment = _data->wait(_active);
|
unique_ptr<Message> segment = _data->wait(_active);
|
||||||
if (!_active)
|
if (!_active)
|
||||||
{
|
|
||||||
if (segment.data)
|
|
||||||
free(segment.data);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
int config = 0;
|
ChannelConfig segmentConfig = getConfig(segment->getInt(OFFSET_AUDIO_FORMAT));
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If settings changed, (re)open audio device
|
// If settings changed, (re)open audio device
|
||||||
if (device == 0 || rate != newRate || channels != newChannels)
|
if (device == 0 || config != segmentConfig)
|
||||||
{
|
{
|
||||||
rate = newRate;
|
config = segmentConfig;
|
||||||
channels = newChannels;
|
|
||||||
|
|
||||||
printf("PCM SETTING %d %d\n", rate, channels);
|
|
||||||
// Close existing device
|
// Close existing device
|
||||||
if (device != 0)
|
if (device != 0)
|
||||||
{
|
{
|
||||||
@@ -117,9 +90,9 @@ void PcmAudio::runner()
|
|||||||
}
|
}
|
||||||
// Configure new spec
|
// Configure new spec
|
||||||
SDL_zero(spec);
|
SDL_zero(spec);
|
||||||
spec.freq = rate;
|
spec.freq = config.rate;
|
||||||
spec.format = AUDIO_S16SYS;
|
spec.format = AUDIO_S16SYS;
|
||||||
spec.channels = static_cast<Uint8>(channels);
|
spec.channels = config.channels;
|
||||||
spec.samples = 4096;
|
spec.samples = 4096;
|
||||||
spec.callback = nullptr;
|
spec.callback = nullptr;
|
||||||
|
|
||||||
@@ -127,11 +100,10 @@ void PcmAudio::runner()
|
|||||||
if (device == 0)
|
if (device == 0)
|
||||||
{
|
{
|
||||||
std::cerr << "Failed to open audio: " << SDL_GetError() << std::endl;
|
std::cerr << "Failed to open audio: " << SDL_GetError() << std::endl;
|
||||||
free(segment.data);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Calculate new buffer target: 0.5s
|
// Calculate new buffer target: 0.5s
|
||||||
targetBytes = rate * channels * sizeof(int16_t) / 2;
|
targetBytes = config.rate * config.channels * sizeof(int16_t) / 2;
|
||||||
bufferedBytes = 0;
|
bufferedBytes = 0;
|
||||||
unpaused = false;
|
unpaused = false;
|
||||||
// Start paused
|
// Start paused
|
||||||
@@ -139,17 +111,16 @@ void PcmAudio::runner()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply volume in-place
|
// Apply volume in-place
|
||||||
int16_t *samples = reinterpret_cast<int16_t *>(segment.data + segment.offset);
|
int16_t *samples = reinterpret_cast<int16_t *>(segment->data());
|
||||||
size_t count = segment.size / sizeof(int16_t);
|
size_t count = segment->length() / sizeof(int16_t);
|
||||||
for (size_t i = 0; i < count; ++i)
|
for (size_t i = 0; i < count; ++i)
|
||||||
{
|
{
|
||||||
samples[i] = static_cast<int16_t>(samples[i] * _volume);
|
samples[i] = static_cast<int16_t>(samples[i] * _volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue audio
|
// Queue audio
|
||||||
SDL_QueueAudio(device, segment.data + segment.offset, segment.size);
|
SDL_QueueAudio(device, segment->data(), segment->length());
|
||||||
bufferedBytes += segment.size;
|
bufferedBytes += segment->length();
|
||||||
free(segment.data);
|
|
||||||
|
|
||||||
// Unpause when enough buffered
|
// Unpause when enough buffered
|
||||||
if (!unpaused && bufferedBytes >= targetBytes)
|
if (!unpaused && bufferedBytes >= targetBytes)
|
||||||
|
|||||||
+22
-6
@@ -7,28 +7,44 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "struct/raw_queue.h"
|
#include "struct/atomic_queue.h"
|
||||||
|
#include "struct/message.h"
|
||||||
#include "helper/error.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:
|
public:
|
||||||
PcmAudio();
|
PcmAudio();
|
||||||
~PcmAudio();
|
~PcmAudio();
|
||||||
|
|
||||||
// Start playing raw PCM data from queue
|
// Start playing raw PCM data from queue
|
||||||
void start(RawQueue* data);
|
void start(AtomicQueue<Message> *data);
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
void setVolume(float vol);
|
void setVolume(float vol);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
ChannelConfig getConfig(int type) const;
|
||||||
void runner();
|
void runner();
|
||||||
void loop(SDL_AudioDeviceID device);
|
void loop(SDL_AudioDeviceID device);
|
||||||
|
|
||||||
RawQueue* _data = nullptr;
|
AtomicQueue<Message> *_data = nullptr;
|
||||||
int _sampleRate = 0;
|
|
||||||
int _channels = 0;
|
|
||||||
|
|
||||||
float _volume = 1.0f;
|
float _volume = 1.0f;
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -178,7 +178,7 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data)
|
|||||||
{
|
{
|
||||||
if (length <= 20)
|
if (length <= 20)
|
||||||
break;
|
break;
|
||||||
videoData.push(data, 20, length - 20);
|
videoData.pushDiscard( std::make_unique<Message>(data, length, 20));
|
||||||
dispose = false;
|
dispose = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -193,19 +193,19 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data)
|
|||||||
memcpy(&channel, data + 8, sizeof(int));
|
memcpy(&channel, data + 8, sizeof(int));
|
||||||
if (channel == 0)
|
if (channel == 0)
|
||||||
{
|
{
|
||||||
audioStream0.push(data, 12, length - 12);
|
audioStream0.pushDiscard(std::make_unique<Message>(data, length, 12));
|
||||||
dispose = false;
|
dispose = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (channel == 1)
|
if (channel == 1)
|
||||||
{
|
{
|
||||||
audioStream1.push(data, 12, length - 12);
|
audioStream1.pushDiscard(std::make_unique<Message>(data, length, 12));
|
||||||
dispose = false;
|
dispose = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (channel == 2)
|
if (channel == 2)
|
||||||
{
|
{
|
||||||
audioStream2.push(data, 12, length - 12);
|
audioStream2.pushDiscard(std::make_unique<Message>(data, length, 12));
|
||||||
dispose = false;
|
dispose = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -1,9 +1,10 @@
|
|||||||
#ifndef SRC_PROTOCOL
|
#ifndef SRC_PROTOCOL
|
||||||
#define 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/iprotocol.h"
|
||||||
#include "helper/settings.h"
|
#include "settings.h"
|
||||||
#include "connector.h"
|
#include "connector.h"
|
||||||
|
|
||||||
#define MAGIC 0x55aa55aa
|
#define MAGIC 0x55aa55aa
|
||||||
@@ -40,10 +41,10 @@ public:
|
|||||||
void sendMove(float dx, float dy);
|
void sendMove(float dx, float dy);
|
||||||
|
|
||||||
Connector connector;
|
Connector connector;
|
||||||
RawQueue videoData;
|
AtomicQueue<Message> videoData;
|
||||||
RawQueue audioStream0;
|
AtomicQueue<Message> audioStream0;
|
||||||
RawQueue audioStream1;
|
AtomicQueue<Message> audioStream1;
|
||||||
RawQueue audioStream2;
|
AtomicQueue<Message> audioStream2;
|
||||||
bool phoneConnected;
|
bool phoneConnected;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef SRC_HELPER_SETTINGS
|
#ifndef SRC_SETTINGS
|
||||||
#define SRC_HELPER_SETTINGS
|
#define SRC_SETTINGS
|
||||||
|
|
||||||
#include "settings_base.h"
|
#include "helper/settings_base.h"
|
||||||
|
|
||||||
// The singleton “Settings” namespace
|
// The singleton “Settings” namespace
|
||||||
class Settings
|
class Settings
|
||||||
@@ -9,14 +9,14 @@ class Settings
|
|||||||
public:
|
public:
|
||||||
static inline Setting<int> vendorid{"vendor-id", 4884};
|
static inline Setting<int> vendorid{"vendor-id", 4884};
|
||||||
static inline Setting<int> productid{"product-id ", 5408};
|
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> width{"width", 720};
|
||||||
static inline Setting<int> height{"height", 576};
|
static inline Setting<int> height{"height", 576};
|
||||||
static inline Setting<int> fps{"fps", 60};
|
static inline Setting<int> fps{"fps", 60};
|
||||||
static inline Setting<int> sourceWidth{"source-width", 720};
|
static inline Setting<int> sourceWidth{"source-width", 720};
|
||||||
static inline Setting<int> sourceHeight{"source-height", 576};
|
static inline Setting<int> sourceHeight{"source-height", 576};
|
||||||
static inline Setting<int> sourceFps{"source-fps", 30};
|
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> scaler{"scaler", 2};
|
||||||
static inline Setting<int> queue{"queue-size", 32};
|
static inline Setting<int> queue{"queue-size", 32};
|
||||||
static inline Setting<int> fontSize{"font-size", 30};
|
static inline Setting<int> fontSize{"font-size", 30};
|
||||||
@@ -28,4 +28,4 @@ private:
|
|||||||
static void trim(std::string &s);
|
static void trim(std::string &s);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* SRC_HELPER_SETTINGS */
|
#endif /* SRC_SETTINGS */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SRC_VIDEO_BUFFER
|
#ifndef SRC_STRUCT_VIDEO_BUFFER
|
||||||
#define SRC_VIDEO_BUFFER
|
#define SRC_STRUCT_VIDEO_BUFFER
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
@@ -7,31 +7,75 @@ extern "C"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <stdexcept>
|
||||||
#include "helper/error.h"
|
|
||||||
|
|
||||||
#define BUFFER_VIDEO_FRAMES 3
|
#define BUFFER_VIDEO_FRAMES 3
|
||||||
|
|
||||||
class VideoBuffer
|
class VideoBuffer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VideoBuffer();
|
VideoBuffer()
|
||||||
~VideoBuffer();
|
{
|
||||||
|
_writing.store(0);
|
||||||
Error allocate(uint16_t width, uint16_t height);
|
_reading.store(-1);
|
||||||
uint16_t width() const { return _width; };
|
_latest.store(-1);
|
||||||
uint16_t height() const { return _height; };
|
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
|
||||||
void reset();
|
{
|
||||||
bool getLatest(AVFrame **frame, uint32_t *id);
|
_ids[i] = 0;
|
||||||
void consumeLatest();
|
_frames[i] = av_frame_alloc();
|
||||||
const AVFrame *writeFrame(uint32_t id);
|
if (!_frames[i])
|
||||||
void commitFrame();
|
{
|
||||||
|
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:
|
private:
|
||||||
void deallocate();
|
|
||||||
|
|
||||||
uint16_t _width;
|
|
||||||
uint16_t _height;
|
|
||||||
std::atomic<int8_t> _latest;
|
std::atomic<int8_t> _latest;
|
||||||
std::atomic<int8_t> _reading;
|
std::atomic<int8_t> _reading;
|
||||||
std::atomic<int8_t> _writing;
|
std::atomic<int8_t> _writing;
|
||||||
@@ -39,4 +83,4 @@ private:
|
|||||||
uint32_t _ids[BUFFER_VIDEO_FRAMES];
|
uint32_t _ids[BUFFER_VIDEO_FRAMES];
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* SRC_VIDEO_BUFFER */
|
#endif /* SRC_STRUCT_VIDEO_BUFFER */
|
||||||
|
|||||||
Reference in New Issue
Block a user