whitescreen workaround (#27)

* Terrible, Horrible, No Good, Very Bad Hack for h264

* Add setting to turn on this terrible hack

* Handle multiple video resolutions

* Use h264bitstream to do this better

* Remove all parameters we don't want

* Cleaning things up a little

* Renaming whitescreen workaround, defaulting to on
This commit is contained in:
Cole Brinsfield 2022-02-12 12:33:02 -08:00 committed by GitHub
parent e2b6985d87
commit 8fa72b4f43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 150 additions and 5 deletions

View File

@ -47,6 +47,7 @@ find_package(Protobuf REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
find_package(rtaudio REQUIRED) find_package(rtaudio REQUIRED)
find_package(aasdk REQUIRED) find_package(aasdk REQUIRED)
find_package(h264 REQUIRED)
find_package(Threads) find_package(Threads)
include(${base_directory}/cmake_modules/gitversion.cmake) include(${base_directory}/cmake_modules/gitversion.cmake)

View File

@ -0,0 +1,26 @@
set (H264_INCLUDE_DIR /usr/local/include/h264bitstream)
set (H264_LIB_DIR /usr/local/lib)
set(H264_FOUND TRUE)
if (H264_FOUND)
if (NOT h264_FIND_QUIETLY)
message(STATUS "Found h264bitstream:")
message(STATUS " - Includes: ${H264_INCLUDE_DIR}")
message(STATUS " - Libraries: ${H264_LIB_DIR}")
endif()
add_library(h264 INTERFACE)
target_include_directories(h264 INTERFACE ${H264_INCLUDE_DIR})
set_target_properties(h264 PROPERTIES INTERFACE_LINK_DIRECTORIES ${H264_LIB_DIR})
target_link_libraries(h264 INTERFACE libh264bitstream.so)
else()
if (h264_FIND_REQUIRED)
if(H264_INCLUDE_DIR)
message(FATAL_ERROR "h264 was found but not built. Perform an in-source build.")
else()
message(FATAL_ERROR "Could not find h264")
endif()
endif()
endif()
mark_as_advanced(H264_INCLUDE_DIR H264_LIBRARIES)

View File

@ -50,6 +50,8 @@ public:
int32_t getOMXLayerIndex() const override; int32_t getOMXLayerIndex() const override;
void setVideoMargins(QRect value) override; void setVideoMargins(QRect value) override;
QRect getVideoMargins() const override; QRect getVideoMargins() const override;
void setWhitescreenWorkaround(bool value) override;
bool getWhitescreenWorkaround() const override;
bool getTouchscreenEnabled() const override; bool getTouchscreenEnabled() const override;
void setTouchscreenEnabled(bool value) override; void setTouchscreenEnabled(bool value) override;
@ -91,6 +93,7 @@ private:
size_t screenDPI_; size_t screenDPI_;
int32_t omxLayerIndex_; int32_t omxLayerIndex_;
QRect videoMargins_; QRect videoMargins_;
bool whitescreenWorkaround_;
bool enableTouchscreen_; bool enableTouchscreen_;
ButtonCodes buttonCodes_; ButtonCodes buttonCodes_;
BluetoothAdapterType bluetoothAdapterType_; BluetoothAdapterType bluetoothAdapterType_;
@ -115,6 +118,7 @@ private:
static const std::string cVideoOMXLayerIndexKey; static const std::string cVideoOMXLayerIndexKey;
static const std::string cVideoMarginWidth; static const std::string cVideoMarginWidth;
static const std::string cVideoMarginHeight; static const std::string cVideoMarginHeight;
static const std::string cVideoWhitescreenWorkaround;
static const std::string cAudioMusicAudioChannelEnabled; static const std::string cAudioMusicAudioChannelEnabled;
static const std::string cAudioSpeechAudioChannelEnabled; static const std::string cAudioSpeechAudioChannelEnabled;

View File

@ -59,6 +59,8 @@ public:
virtual int32_t getOMXLayerIndex() const = 0; virtual int32_t getOMXLayerIndex() const = 0;
virtual void setVideoMargins(QRect value) = 0; virtual void setVideoMargins(QRect value) = 0;
virtual QRect getVideoMargins() const = 0; virtual QRect getVideoMargins() const = 0;
virtual void setWhitescreenWorkaround(bool value) = 0;
virtual bool getWhitescreenWorkaround() const = 0;
virtual bool getTouchscreenEnabled() const = 0; virtual bool getTouchscreenEnabled() const = 0;
virtual void setTouchscreenEnabled(bool value) = 0; virtual void setTouchscreenEnabled(bool value) = 0;

View File

@ -78,6 +78,8 @@ private:
static GstPadProbeReturn convertProbe(GstPad* pad, GstPadProbeInfo* info, void*); static GstPadProbeReturn convertProbe(GstPad* pad, GstPadProbeInfo* info, void*);
static gboolean busCallback(GstBus*, GstMessage* message, gpointer*); static gboolean busCallback(GstBus*, GstMessage* message, gpointer*);
bool firstHeaderParsed = false;
QGst::ElementPtr videoSink_; QGst::ElementPtr videoSink_;
QQuickWidget* videoWidget_; QQuickWidget* videoWidget_;
GstElement* vidPipeline_; GstElement* vidPipeline_;

View File

@ -115,6 +115,7 @@ target_link_libraries(openauto PUBLIC
Threads::Threads Threads::Threads
${Boost_LIBRARIES} ${Boost_LIBRARIES}
aasdk aasdk
h264
rtaudio rtaudio
Qt5::Bluetooth Qt5::Bluetooth
Qt5::MultimediaWidgets Qt5::MultimediaWidgets

View File

@ -35,6 +35,8 @@ const std::string Configuration::cVideoScreenDPIKey = "Video.ScreenDPI";
const std::string Configuration::cVideoOMXLayerIndexKey = "Video.OMXLayerIndex"; const std::string Configuration::cVideoOMXLayerIndexKey = "Video.OMXLayerIndex";
const std::string Configuration::cVideoMarginWidth = "Video.MarginWidth"; const std::string Configuration::cVideoMarginWidth = "Video.MarginWidth";
const std::string Configuration::cVideoMarginHeight = "Video.MarginHeight"; const std::string Configuration::cVideoMarginHeight = "Video.MarginHeight";
const std::string Configuration::cVideoWhitescreenWorkaround = "Video.WhitesreenWorkaround";
const std::string Configuration::cAudioMusicAudioChannelEnabled = "Audio.MusicAudioChannelEnabled"; const std::string Configuration::cAudioMusicAudioChannelEnabled = "Audio.MusicAudioChannelEnabled";
const std::string Configuration::cAudioSpeechAudioChannelEnabled = "Audio.SpeechAudioChannelEnabled"; const std::string Configuration::cAudioSpeechAudioChannelEnabled = "Audio.SpeechAudioChannelEnabled";
@ -93,6 +95,7 @@ void Configuration::load()
omxLayerIndex_ = iniConfig.get<int32_t>(cVideoOMXLayerIndexKey, 1); omxLayerIndex_ = iniConfig.get<int32_t>(cVideoOMXLayerIndexKey, 1);
videoMargins_ = QRect(0, 0, iniConfig.get<int32_t>(cVideoMarginWidth, 0), iniConfig.get<int32_t>(cVideoMarginHeight, 0)); videoMargins_ = QRect(0, 0, iniConfig.get<int32_t>(cVideoMarginWidth, 0), iniConfig.get<int32_t>(cVideoMarginHeight, 0));
whitescreenWorkaround_ = iniConfig.get<bool>(cVideoWhitescreenWorkaround, true);
enableTouchscreen_ = iniConfig.get<bool>(cInputEnableTouchscreenKey, true); enableTouchscreen_ = iniConfig.get<bool>(cInputEnableTouchscreenKey, true);
this->readButtonCodes(iniConfig); this->readButtonCodes(iniConfig);
@ -130,6 +133,7 @@ void Configuration::reset()
screenDPI_ = 140; screenDPI_ = 140;
omxLayerIndex_ = 1; omxLayerIndex_ = 1;
videoMargins_ = QRect(0, 0, 0, 0); videoMargins_ = QRect(0, 0, 0, 0);
whitescreenWorkaround_ = true;
enableTouchscreen_ = true; enableTouchscreen_ = true;
buttonCodes_.clear(); buttonCodes_.clear();
bluetoothAdapterType_ = BluetoothAdapterType::NONE; bluetoothAdapterType_ = BluetoothAdapterType::NONE;
@ -151,6 +155,7 @@ void Configuration::save()
iniConfig.put<int32_t>(cVideoOMXLayerIndexKey, omxLayerIndex_); iniConfig.put<int32_t>(cVideoOMXLayerIndexKey, omxLayerIndex_);
iniConfig.put<uint32_t>(cVideoMarginWidth, videoMargins_.width()); iniConfig.put<uint32_t>(cVideoMarginWidth, videoMargins_.width());
iniConfig.put<uint32_t>(cVideoMarginHeight, videoMargins_.height()); iniConfig.put<uint32_t>(cVideoMarginHeight, videoMargins_.height());
iniConfig.put<bool>(cVideoWhitescreenWorkaround, whitescreenWorkaround_);
iniConfig.put<bool>(cInputEnableTouchscreenKey, enableTouchscreen_); iniConfig.put<bool>(cInputEnableTouchscreenKey, enableTouchscreen_);
this->writeButtonCodes(iniConfig); this->writeButtonCodes(iniConfig);
@ -240,6 +245,16 @@ QRect Configuration::getVideoMargins() const
return videoMargins_; return videoMargins_;
} }
void Configuration::setWhitescreenWorkaround(bool value)
{
whitescreenWorkaround_ = value;
}
bool Configuration::getWhitescreenWorkaround() const
{
return whitescreenWorkaround_;
}
bool Configuration::getTouchscreenEnabled() const bool Configuration::getTouchscreenEnabled() const
{ {
return enableTouchscreen_; return enableTouchscreen_;

View File

@ -20,7 +20,13 @@
#include "aasdk/Common/Data.hpp" #include "aasdk/Common/Data.hpp"
#include "openauto/Projection/GSTVideoOutput.hpp" #include "openauto/Projection/GSTVideoOutput.hpp"
#include "OpenautoLog.hpp" #include "OpenautoLog.hpp"
#include "h264_stream.h"
#include <QTimer> #include <QTimer>
// these are needed only for pretty printing of data, to be removed
#include <sstream>
#include <string>
#include <iostream>
#include <iomanip>
namespace openauto namespace openauto
{ {
@ -176,17 +182,103 @@ bool GSTVideoOutput::init()
void GSTVideoOutput::write(uint64_t timestamp, const aasdk::common::DataConstBuffer& buffer) void GSTVideoOutput::write(uint64_t timestamp, const aasdk::common::DataConstBuffer& buffer)
{ {
GstBuffer* buffer_ = gst_buffer_new_and_alloc(buffer.size); if(!firstHeaderParsed && this->configuration_->getWhitescreenWorkaround())
gst_buffer_fill(buffer_, 0, buffer.cdata, buffer.size);
int ret = gst_app_src_push_buffer((GstAppSrc*)vidSrc_, buffer_);
if(ret != GST_FLOW_OK)
{ {
OPENAUTO_LOG(info) << "[GSTVideoOutput] push buffer returned " << ret << " for " << buffer.size << "bytes"; // I really really really hate this.
// Raspberry Pi hardware h264 decode appears broken if video_signal_type VUI parameters are given in the h264 header
// And we don't have control over Android Auto putting these parameters in (which it does.. on some model phones)
// So we pull in h264bitstream to edit this header on the fly
// This is not a fix, I want to be very clear about that. I don't know what else I'm breaking, or run the
// risk of breaking by doing this. This code should only remain here as long as the Pi Engineers haven't released
// a firmware/driver fix for this yet. An issue has been opened upstream at https://github.com/raspberrypi/firmware/issues/1673
// Android Auto seems nice enough to always start a message with a new h264 packet,
// but that doesn't mean we don't have multiple within the message.
// So if we have a message that _could_ fit two packets (which are delimited by 0x00000001)
// then we try to find the second and save the data it contains, while editing and replacing the first.
// This header should also always be within the first video message we receive from a device... I think
std::vector<uint8_t> delimit_sequence{0x00, 0x00, 0x00, 0x01};
std::vector<uint8_t>::iterator sequence_split;
std::vector<uint8_t> incoming_buffer(&buffer.cdata[0], &buffer.cdata[buffer.size]);
int nal_start, nal_end;
uint8_t* buf = (uint8_t *) buffer.cdata;
int len = buffer.size;
h264_stream_t* h = h264_new();
// finds the first NAL packet
find_nal_unit(buf, len, &nal_start, &nal_end);
// parses it
read_nal_unit(h, &buf[nal_start], nal_end - nal_start);
// wipe all the color description stuff that breaks Pis
h->sps->vui.video_signal_type_present_flag = 0x00;
h->sps->vui.video_format = 0x00;
h->sps->vui.video_full_range_flag = 0x00;
h->sps->vui.colour_description_present_flag = 0x00;
h->sps->vui.video_format = 0x00;
h->sps->vui.video_full_range_flag = 0x00;
h->sps->vui.colour_description_present_flag = 0x00;
h->sps->vui.colour_primaries = 0x00;
h->sps->vui.transfer_characteristics = 0x00;
h->sps->vui.matrix_coefficients = 0x00;
// grab some storage
uint8_t* out_buf = new uint8_t[30];
// write it to the storage's 3rd index, because h264bitstream seems to have a bug
// where it both doesn't write the delimiter, and it prepends a leading 0x00
len = write_nal_unit(h, &out_buf[3], 30) + 3;
// write the delimiter back
out_buf[0] = 0x00;
out_buf[1] = 0x00;
out_buf[2] = 0x00;
out_buf[3] = 0x01;
// output to gstreamer
GstBuffer* buffer_ = gst_buffer_new_and_alloc(len);
gst_buffer_fill(buffer_, 0, out_buf, len);
int ret = gst_app_src_push_buffer((GstAppSrc*)vidSrc_, buffer_);
if(ret != GST_FLOW_OK)
{
OPENAUTO_LOG(info) << "[GSTVideoOutput] Injecting header failed";
}
// then check if there's data we need to save
if(incoming_buffer.size() >= 8){
sequence_split = std::search(incoming_buffer.begin()+4, incoming_buffer.end(), delimit_sequence.begin(), delimit_sequence.end());
if(sequence_split != incoming_buffer.end()){
std::vector<uint8_t> incoming_data_saved(sequence_split, incoming_buffer.end());
GstBuffer* buffer_ = gst_buffer_new_and_alloc(incoming_data_saved.size());
gst_buffer_fill(buffer_, 0, incoming_data_saved.data(), incoming_data_saved.size());
int ret = gst_app_src_push_buffer((GstAppSrc*)vidSrc_, buffer_);
if(ret != GST_FLOW_OK)
{
OPENAUTO_LOG(info) << "[GSTVideoOutput] Injecting partial header failed";
}
}
}
OPENAUTO_LOG(info) << "[GSTVideoOutput] Intercepted and replaced h264 header";
firstHeaderParsed=true;
}
else
{
GstBuffer* buffer_ = gst_buffer_new_and_alloc(buffer.size);
gst_buffer_fill(buffer_, 0, buffer.cdata, buffer.size);
int ret = gst_app_src_push_buffer((GstAppSrc*)vidSrc_, buffer_);
if(ret != GST_FLOW_OK)
{
OPENAUTO_LOG(info) << "[GSTVideoOutput] push buffer returned " << ret << " for " << buffer.size << "bytes";
}
} }
} }
void GSTVideoOutput::onStartPlayback() void GSTVideoOutput::onStartPlayback()
{ {
firstHeaderParsed = false;
if(activeCallback_ != nullptr) if(activeCallback_ != nullptr)
{ {
activeCallback_(true); activeCallback_(true);
@ -215,6 +307,8 @@ void GSTVideoOutput::stop()
void GSTVideoOutput::onStopPlayback() void GSTVideoOutput::onStopPlayback()
{ {
firstHeaderParsed = false;
if(activeCallback_ != nullptr) if(activeCallback_ != nullptr)
{ {
activeCallback_(false); activeCallback_(false);