Skip to content

Commit

Permalink
Allow chroma to be written to a separate output file.
Browse files Browse the repository at this point in the history
This matches the output from vhs-decode, which ld-analyse already
accepts.
  • Loading branch information
atsampson committed Aug 22, 2022
1 parent 82ef3d2 commit 288c3d1
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 40 deletions.
75 changes: 48 additions & 27 deletions tools/ld-chroma-decoder/encoder/encoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@

#include "encoder.h"

Encoder::Encoder(QFile &_rgbFile, QFile &_tbcFile, LdDecodeMetaData &_metaData)
: rgbFile(_rgbFile), tbcFile(_tbcFile), metaData(_metaData)
Encoder::Encoder(QFile &_rgbFile, QFile &_tbcFile, QFile &_chromaFile, LdDecodeMetaData &_metaData)
: rgbFile(_rgbFile), tbcFile(_tbcFile), chromaFile(_chromaFile), metaData(_metaData)
{
}

Expand Down Expand Up @@ -119,8 +119,8 @@ bool Encoder::encodeField(qint32 fieldNo)
std::vector<double> outputC(videoParameters.fieldWidth);
std::vector<double> outputVBS(videoParameters.fieldWidth);

// TBC data is unsigned 16-bit values in native byte order
std::vector<quint16> outputLine(videoParameters.fieldWidth);
// Buffer for conversion
std::vector<quint16> outputBuffer(videoParameters.fieldWidth);

for (qint32 frameLine = 0; frameLine < 2 * videoParameters.fieldHeight; frameLine++) {
// Skip lines that aren't in this field
Expand All @@ -135,30 +135,16 @@ bool Encoder::encodeField(qint32 fieldNo)
}
encodeLine(fieldNo, frameLine, rgbData, outputC, outputVBS);

// Scale to a 16-bit output sample and limit the excursion to the
// permitted sample values. [EBU p6] [SMPTE p6]
//
// With PAL line-locked sampling, some colours (e.g. the yellow
// colourbar) can result in values outside this range because there
// isn't enough headroom.
for (qint32 x = 0; x < videoParameters.fieldWidth; x++) {
const double scaled = ((outputC[x] + outputVBS[x])
* (videoParameters.white16bIre - videoParameters.black16bIre)) + videoParameters.black16bIre;
outputLine[x] = qBound(static_cast<double>(0x0100), scaled, static_cast<double>(0xFEFF));
}

// Write the line to the TBC file
const char *outputData = reinterpret_cast<const char *>(outputLine.data());
qint64 remainBytes = outputLine.size() * 2;
qint64 posBytes = 0;
while (remainBytes > 0) {
qint64 count = tbcFile.write(outputData + posBytes, remainBytes);
if (count < 0) {
qCritical() << "Error writing to output file";
return false;
if (chromaFile.isOpen()) {
// Write C and VBS to separate output files
if (!writeLine(outputC, outputBuffer, true, chromaFile)) return false;
if (!writeLine(outputVBS, outputBuffer, false, tbcFile)) return false;
} else {
// Combine C and VBS into a single output file
for (qint32 x = 0; x < videoParameters.fieldWidth; x++) {
outputVBS[x] += outputC[x];
}
remainBytes -= count;
posBytes += count;
if (!writeLine(outputVBS, outputBuffer, false, tbcFile)) return false;
}
}

Expand All @@ -169,3 +155,38 @@ bool Encoder::encodeField(qint32 fieldNo)

return true;
}

bool Encoder::writeLine(const std::vector<double> &input, std::vector<quint16> &buffer, bool isChroma, QFile &file)
{
// Scale to a 16-bit output sample and limit the excursion to the
// permitted sample values. [EBU p6] [SMPTE p6]
//
// With PAL line-locked sampling, some colours (e.g. the yellow
// colourbar) can result in values outside this range because there
// isn't enough headroom.
//
// Separate chroma is scaled like the normal signal, but centred on 0x7FFF.
const double scale = videoParameters.white16bIre - videoParameters.black16bIre;
const double offset = isChroma ? 0x7FFF : videoParameters.black16bIre;
for (qint32 x = 0; x < videoParameters.fieldWidth; x++) {
const double scaled = qBound(static_cast<double>(0x0100), (input[x] * scale) + offset, static_cast<double>(0xFEFF));
buffer[x] = static_cast<quint16>(scaled);
}

// Write the converted line to the output file.
// TBC data is unsigned 16-bit values in native byte order.
const char *outputData = reinterpret_cast<const char *>(buffer.data());
qint64 remainBytes = buffer.size() * 2;
qint64 posBytes = 0;
while (remainBytes > 0) {
qint64 count = file.write(outputData + posBytes, remainBytes);
if (count < 0) {
qCritical() << "Error writing to output file";
return false;
}
remainBytes -= count;
posBytes += count;
}

return true;
}
7 changes: 6 additions & 1 deletion tools/ld-chroma-decoder/encoder/encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Encoder
// This only sets the member variables it takes as parameters; subclasses
// must initialise the VideoParameters, compute the active region and
// resize rgbFrame.
Encoder(QFile &rgbFile, QFile &tbcFile, LdDecodeMetaData &metaData);
Encoder(QFile &rgbFile, QFile &tbcFile, QFile &chromaFile, LdDecodeMetaData &metaData);

// Encode RGB stream to TBC.
// Returns true on success; on failure, prints an error and returns false.
Expand All @@ -59,8 +59,13 @@ class Encoder
virtual void encodeLine(qint32 fieldNo, qint32 frameLine, const quint16 *rgbData,
std::vector<double> &outputC, std::vector<double> &outputVBS) = 0;

// Scale and write a line of data to one of the output files.
// Returns true on success; on failure, prints an error and returns false.
bool writeLine(const std::vector<double> &input, std::vector<quint16> &buffer, bool isChroma, QFile &file);

QFile &rgbFile;
QFile &tbcFile;
QFile &chromaFile;
LdDecodeMetaData &metaData;

LdDecodeMetaData::VideoParameters videoParameters;
Expand Down
27 changes: 21 additions & 6 deletions tools/ld-chroma-decoder/encoder/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
main.cpp
ld-chroma-encoder - Composite video encoder
Copyright (C) 2019-2020 Adam Sampson
Copyright (C) 2019-2022 Adam Sampson
This file is part of ld-decode-tools.
Expand Down Expand Up @@ -94,6 +94,10 @@ int main(int argc, char *argv[])
// Positional argument to specify output video file
parser.addPositionalArgument("output", QCoreApplication::translate("main", "Specify output TBC file"));

// Positional argument to specify chroma output video file
parser.addPositionalArgument("chroma", QCoreApplication::translate("main", "Specify chroma output TBC file (optional)"),
"[chroma]");

// Process the command line options and arguments given by the user
parser.process(a);

Expand All @@ -106,10 +110,14 @@ int main(int argc, char *argv[])
// Get the arguments from the parser
QString inputFileName;
QString outputFileName;
QString chromaFileName;
QStringList positionalArguments = parser.positionalArguments();
if (positionalArguments.count() == 2) {
if (positionalArguments.count() == 2 || positionalArguments.count() == 3) {
inputFileName = positionalArguments.at(0);
outputFileName = positionalArguments.at(1);
if (positionalArguments.count() > 2) {
chromaFileName = positionalArguments.at(2);
}
} else {
// Quit with error
qCritical("You must specify the input RGB and output TBC files");
Expand Down Expand Up @@ -167,22 +175,29 @@ int main(int argc, char *argv[])
}
}

// Open the output file
// Open the main output file
QFile tbcFile(outputFileName);
if (!tbcFile.open(QFile::WriteOnly)) {
qCritical() << "Cannot open output file:" << outputFileName;
return -1;
}

// Open the chroma output file, if specified
QFile chromaFile(chromaFileName);
if (chromaFileName != "" && !chromaFile.open(QFile::WriteOnly)) {
qCritical() << "Cannot open chroma output file:" << chromaFileName;
return -1;
}

// Encode the data
LdDecodeMetaData metaData;
if( system == NTSC ) {
NTSCEncoder encoder(rgbFile, tbcFile, metaData, chromaMode, addSetup);
if (system == NTSC) {
NTSCEncoder encoder(rgbFile, tbcFile, chromaFile, metaData, chromaMode, addSetup);
if (!encoder.encode()) {
return -1;
}
} else {
PALEncoder encoder(rgbFile, tbcFile, metaData, scLocked);
PALEncoder encoder(rgbFile, tbcFile, chromaFile, metaData, scLocked);
if (!encoder.encode()) {
return -1;
}
Expand Down
5 changes: 3 additions & 2 deletions tools/ld-chroma-decoder/encoder/ntscencoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@
#include <array>
#include <cmath>

NTSCEncoder::NTSCEncoder(QFile &_rgbFile, QFile &_tbcFile, LdDecodeMetaData &_metaData, ChromaMode _chromaMode, bool _addSetup)
: Encoder(_rgbFile, _tbcFile, _metaData), chromaMode(_chromaMode), addSetup(_addSetup)
NTSCEncoder::NTSCEncoder(QFile &_rgbFile, QFile &_tbcFile, QFile &_chromaFile, LdDecodeMetaData &_metaData,
ChromaMode _chromaMode, bool _addSetup)
: Encoder(_rgbFile, _tbcFile, _chromaFile, _metaData), chromaMode(_chromaMode), addSetup(_addSetup)
{
// NTSC subcarrier frequency [Poynton p511]
videoParameters.fSC = 315.0e6 / 88.0;
Expand Down
3 changes: 2 additions & 1 deletion tools/ld-chroma-decoder/encoder/ntscencoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ enum ChromaMode {
class NTSCEncoder : public Encoder
{
public:
NTSCEncoder(QFile &rgbFile, QFile &tbcFile, LdDecodeMetaData &metaData, ChromaMode chromaMode, bool addSetup);
NTSCEncoder(QFile &rgbFile, QFile &tbcFile, QFile &chromaFile, LdDecodeMetaData &metaData,
ChromaMode chromaMode, bool addSetup);

protected:
virtual void getFieldMetadata(qint32 fieldNo, LdDecodeMetaData::Field &fieldData);
Expand Down
4 changes: 2 additions & 2 deletions tools/ld-chroma-decoder/encoder/palencoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
#include <array>
#include <cmath>

PALEncoder::PALEncoder(QFile &_rgbFile, QFile &_tbcFile, LdDecodeMetaData &_metaData, bool _scLocked)
: Encoder(_rgbFile, _tbcFile, _metaData), scLocked(_scLocked)
PALEncoder::PALEncoder(QFile &_rgbFile, QFile &_tbcFile, QFile &_chromaFile, LdDecodeMetaData &_metaData, bool _scLocked)
: Encoder(_rgbFile, _tbcFile, _chromaFile, _metaData), scLocked(_scLocked)
{
// PAL subcarrier frequency [Poynton p529] [EBU p5]
videoParameters.fSC = 4433618.75;
Expand Down
2 changes: 1 addition & 1 deletion tools/ld-chroma-decoder/encoder/palencoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
class PALEncoder : public Encoder
{
public:
PALEncoder(QFile &rgbFile, QFile &tbcFile, LdDecodeMetaData &metaData, bool scLocked);
PALEncoder(QFile &rgbFile, QFile &tbcFile, QFile &chromaFile, LdDecodeMetaData &metaData, bool scLocked);

private:
virtual void getFieldMetadata(qint32 fieldNo, LdDecodeMetaData::Field &fieldData);
Expand Down

0 comments on commit 288c3d1

Please sign in to comment.