aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorghost <c42723f8913e7023435c322995e52208d7cf860640ca75118f526cca77cb5bab@yggmail>2022-01-05 00:35:26 +0300
committerDaniil Cherednik <dan.cherednik@gmail.com>2022-02-20 01:05:40 +0300
commita8690dd6b204eae3a5e8d8466aa2abab73cdad48 (patch)
tree1d71801a076f9d5f1fe549132c920a71b36a29d5
parentcf8f27830d19e5ec2524a9dc6b50c8a30869917f (diff)
downloadatracdenc-a8690dd6b204eae3a5e8d8466aa2abab73cdad48.tar.gz
Support for RealMedia output file format.
ATRAC3 is one of codecs used for RealMedia player.
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/atrac/atrac3_bitstream.h4
-rw-r--r--src/atrac3denc.cpp8
-rw-r--r--src/main.cpp37
-rw-r--r--src/rm.cpp282
-rw-r--r--src/rm.h24
6 files changed, 340 insertions, 16 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ad1761b..c0c22fe 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -69,6 +69,7 @@ set(SOURCE_ATRACDENC_IMPL
atrac/atrac_psy_common.cpp
atrac/atrac1_bitalloc.cpp
oma.cpp
+ rm.cpp
atrac3denc.cpp
atrac/atrac3.cpp
atrac/atrac3_bitstream.cpp
diff --git a/src/atrac/atrac3_bitstream.h b/src/atrac/atrac3_bitstream.h
index 02f05bc..152437b 100644
--- a/src/atrac/atrac3_bitstream.h
+++ b/src/atrac/atrac3_bitstream.h
@@ -53,7 +53,7 @@ private:
std::vector<uint8_t> SubGroupMap;
std::vector<const TTonalBlock*> SubGroupPtr;
};
- TOma* Container;
+ ICompressedOutput* Container;
const TContainerParams Params;
const uint32_t BfuIdxConst;
std::vector<char> OutBuffer;
@@ -85,7 +85,7 @@ private:
const std::vector<uint32_t>& allocTable,
NBitStream::TBitStream* bitStream);
public:
- TAtrac3BitStreamWriter(TOma* container, const TContainerParams& params, uint32_t bfuIdxConst) //no mono mode for atrac3
+ TAtrac3BitStreamWriter(ICompressedOutput* container, const TContainerParams& params, uint32_t bfuIdxConst) //no mono mode for atrac3
: Container(container)
, Params(params)
, BfuIdxConst(bfuIdxConst)
diff --git a/src/atrac3denc.cpp b/src/atrac3denc.cpp
index 1da3641..1208963 100644
--- a/src/atrac3denc.cpp
+++ b/src/atrac3denc.cpp
@@ -290,13 +290,7 @@ void TAtrac3Encoder::Matrixing()
TPCMEngine<TFloat>::TProcessLambda TAtrac3Encoder::GetLambda()
{
- TOma* omaptr = dynamic_cast<TOma*>(Oma.get());
- if (!omaptr) {
- std::cerr << "Wrong container" << std::endl;
- abort();
- }
-
- TAtrac3BitStreamWriter* bitStreamWriter = new TAtrac3BitStreamWriter(omaptr, *Params.ConteinerParams, Params.BfuIdxConst);
+ std::shared_ptr<TAtrac3BitStreamWriter> bitStreamWriter(new TAtrac3BitStreamWriter(Oma.get(), *Params.ConteinerParams, Params.BfuIdxConst));
return [this, bitStreamWriter](TFloat* data, const TPCMEngine<TFloat>::ProcessMeta& meta) {
using TSce = TAtrac3BitStreamWriter::TSingleChannelElement;
diff --git a/src/main.cpp b/src/main.cpp
index c7d2eec..e7492a9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -25,6 +25,7 @@
#include "pcmengin.h"
#include "wav.h"
#include "aea.h"
+#include "rm.h"
#include "config.h"
#include "atrac1denc.h"
#include "atrac3denc.h"
@@ -78,6 +79,15 @@ static string GetHelp()
/*"\n --nogaincontrol disable gain control (ATRAC3)"*/
}
+static string GetFileExt(const string& path) {
+ size_t dotPos = path.rfind('.');
+ std::string ext;
+ if (dotPos != std::string::npos && dotPos < path.size()) {
+ ext = path.substr(dotPos + 1);
+ }
+ return ext;
+}
+
static int checkedStoi(const char* data, int min, int max, int def)
{
int tmp = 0;
@@ -197,17 +207,30 @@ static void PrepareAtrac3Encoder(const string& inFile,
std::cout << "bitrate " << encoderSettings.ConteinerParams->Bitrate << std::endl;
const int numChannels = encoderSettings.SourceChannels;
*totalSamples = wavIO->GetTotalSamples();
- const uint64_t numFrames = numChannels * ((*totalSamples) / 512);
+ const uint64_t numFrames = (*totalSamples) / 1024;
if (numFrames >= UINT32_MAX) {
std::cerr << "Number of input samples exceeds output format limitation,"
"the result will be incorrect" << std::endl;
}
- TCompressedOutputPtr omaIO = TCompressedOutputPtr(new TOma(outFile,
- "test",
- numChannels,
- (int32_t)numFrames, OMAC_ID_ATRAC3,
- encoderSettings.ConteinerParams->FrameSz,
- encoderSettings.ConteinerParams->Js));
+
+ const string ext = GetFileExt(outFile);
+
+ TCompressedOutputPtr omaIO;
+
+ if (ext == "rm") {
+ omaIO = CreateRmOutput(outFile, "test", numChannels,
+ numFrames, encoderSettings.ConteinerParams->FrameSz,
+ encoderSettings.ConteinerParams->Js);
+ } else {
+
+ omaIO.reset(new TOma(outFile,
+ "test",
+ numChannels,
+ (int32_t)numFrames, OMAC_ID_ATRAC3,
+ encoderSettings.ConteinerParams->FrameSz,
+ encoderSettings.ConteinerParams->Js));
+ }
+
pcmEngine->reset(new TPCMEngine<TFloat>(4096,
numChannels,
TPCMEngine<TFloat>::TReaderPtr(wavIO->GetPCMReader<TFloat>())));
diff --git a/src/rm.cpp b/src/rm.cpp
new file mode 100644
index 0000000..656cbd7
--- /dev/null
+++ b/src/rm.cpp
@@ -0,0 +1,282 @@
+/*
+ * This file is part of AtracDEnc.
+ *
+ * AtracDEnc is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * AtracDEnc is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with AtracDEnc; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "rm.h"
+
+#include "lib/endian_tools.h"
+#include <cstring>
+#include <iostream>
+#include <cmath>
+#include <assert.h>
+
+/*
+ * This file result of reading https://wiki.multimedia.cx/index.php/RealMedia, FFMpeg code, and some experiments
+ * Produce ra5 stream compatible with proprietary RA player
+ */
+
+/*
+ * TODO:
+ * - title support
+ */
+namespace {
+
+using std::string;
+
+FILE* OpenFile(const string& filename) {
+ FILE* fp = fopen(filename.c_str(), "wb");
+ if (!fp)
+ throw std::runtime_error("Can't open file to write");
+ return fp;
+}
+
+constexpr size_t RMF_HEADER_SZ = 18;
+void WriteRMF(FILE* f) {
+ char buf[RMF_HEADER_SZ] = {
+ '.', 'R', 'M', 'F'};
+ *reinterpret_cast<uint32_t*>(buf + 4) = swapbyte32_on_le(18);
+ *reinterpret_cast<uint16_t*>(buf + 8) = 0; //chunk version
+ *reinterpret_cast<uint32_t*>(buf + 10) = 0; //file version
+ *reinterpret_cast<uint32_t*>(buf + 14) = swapbyte32_on_le(4); //number of headers
+ if (fwrite(buf, RMF_HEADER_SZ, 1, f) != 1)
+ throw std::runtime_error("Can't write RMF header");
+}
+
+constexpr size_t CODEC_DATA_SZ = 92;
+constexpr char RA_MIME[] = "audio/x-pn-realaudio";
+constexpr char RA_DESC[] = "Audio Stream";
+static_assert(sizeof(char) == 1);
+constexpr size_t MDPR_HEADER_SZ = 42 + sizeof(RA_MIME) + sizeof(RA_DESC) + CODEC_DATA_SZ;
+
+void FillCodecData(char* buf, uint32_t frameSize, uint8_t numChannels, bool jointStereo, uint32_t bitrate) {
+ *reinterpret_cast<uint32_t*>(buf + 0) = swapbyte32_on_le(CODEC_DATA_SZ - 4); // -4 - without size of `size` field
+ buf[4] = '.';
+ buf[5] = 'r';
+ buf[6] = 'a';
+ buf[7] = 0xfd;
+ *reinterpret_cast<uint16_t*>(buf + 8) = swapbyte16_on_le(5); // version
+ *reinterpret_cast<uint16_t*>(buf + 10) = 0; // unused
+ buf[12] = '.';
+ buf[13] = 'r';
+ buf[14] = 'a';
+ buf[15] = '5';
+ *reinterpret_cast<uint32_t*>(buf + 16) = swapbyte32_on_le(0x01b53530); // copypaste from FFmpeg
+ *reinterpret_cast<uint16_t*>(buf + 20) = swapbyte16_on_le(5); // version2
+ *reinterpret_cast<uint32_t*>(buf + 22) = swapbyte32_on_le(0); // header size
+ *reinterpret_cast<uint16_t*>(buf + 26) = swapbyte16_on_le(2); // flavor ???
+ *reinterpret_cast<uint32_t*>(buf + 28) = swapbyte32_on_le(frameSize * 3); // codec frame size
+ *reinterpret_cast<uint32_t*>(buf + 32) = swapbyte32_on_le(0x51540); // copypaste from FFmpeg
+ *reinterpret_cast<uint32_t*>(buf + 36) = swapbyte32_on_le(bitrate / 8 * 60); // bytes per minute
+ *reinterpret_cast<uint32_t*>(buf + 40) = swapbyte32_on_le(bitrate / 8 * 60); // maybe bytes per minute???
+ *reinterpret_cast<uint16_t*>(buf + 44) = swapbyte16_on_le(1); // sub packet h (no interleave)
+ *reinterpret_cast<uint16_t*>(buf + 46) = swapbyte16_on_le(frameSize * 3); // frame size
+ *reinterpret_cast<uint16_t*>(buf + 48) = swapbyte16_on_le(frameSize); // sub packet sz
+ *reinterpret_cast<uint16_t*>(buf + 50) = 0; // ???
+ buf[52] = 0;
+ buf[53] = 0;
+
+ *reinterpret_cast<uint16_t*>(buf + 54) = swapbyte16_on_le(44100); // sample rate
+ buf[56] = 0;
+ buf[57] = 0;
+ *reinterpret_cast<uint16_t*>(buf + 58) = swapbyte16_on_le(44100); // sample rate
+ *reinterpret_cast<uint16_t*>(buf + 60) = 0;
+ *reinterpret_cast<uint16_t*>(buf + 62) = swapbyte16_on_le(16); //sample size
+ *reinterpret_cast<uint16_t*>(buf + 64) = swapbyte16_on_le(2); //channels
+ buf[66] = 'g';
+ buf[67] = 'e';
+ buf[68] = 'n';
+ buf[69] = 'r';
+ buf[70] = 'a';
+ buf[71] = 't';
+ buf[72] = 'r';
+ buf[73] = 'c';
+
+ buf[74] = 0x01; // ???
+ buf[75] = 0x07; // ???
+ buf[76] = 0;
+
+ buf[77] = 0;
+
+ *reinterpret_cast<uint32_t*>(buf + 78) = swapbyte32_on_le(10);
+ *reinterpret_cast<uint32_t*>(buf + 82) = swapbyte32_on_le(4);
+ *reinterpret_cast<uint16_t*>(buf + 86) = swapbyte16_on_le(1024 * numChannels);
+ *reinterpret_cast<uint16_t*>(buf + 88) = swapbyte16_on_le(0x88E);
+ *reinterpret_cast<uint16_t*>(buf + 90) = swapbyte16_on_le(jointStereo ? 0x12 : 0x2);
+}
+
+void WriteDATA(FILE* f, uint32_t numFrames) {
+ constexpr size_t DATA_HEADER_SZ = 18;
+
+ char buf[DATA_HEADER_SZ] = {
+ 'D', 'A', 'T', 'A'};
+ *reinterpret_cast<uint32_t*>(buf + 4) = 0xffffffff; //size of whole data chunk, will be patched at the end
+ *reinterpret_cast<uint16_t*>(buf + 8) = 0; //chunk version
+ *reinterpret_cast<uint32_t*>(buf + 10) = swapbyte32_on_le(numFrames);
+ *reinterpret_cast<uint32_t*>(buf + 14) = swapbyte32_on_le(0); //offset next DATA header (not used)
+
+ if (fwrite(buf, DATA_HEADER_SZ, 1, f) != 1)
+ throw std::runtime_error("Can't write DATA header");
+}
+
+void scramble_data(const char* input, char* out, size_t bytes) {
+ const uint32_t* buf = reinterpret_cast<const uint32_t*>(input);
+ uint32_t* o = reinterpret_cast<uint32_t*>(out);
+ uint32_t c = swapbyte32_on_le(0x537F6103U);
+ static_assert(sizeof(uint32_t) / sizeof(char) == 4);
+
+ for (size_t i = 0; i < bytes / 4; i++) {
+ o[i] = c ^ buf[i];
+ }
+}
+
+} //namespace
+
+class TRm : public ICompressedOutput {
+public:
+ TRm(const std::string& filename, const std::string& title, uint8_t numChannels,
+ uint32_t numFrames, uint32_t frameSize, bool jointStereo)
+ : File_(OpenFile(filename))
+ , FrameDuration_((1000.0 * 1024.0 / 44100.0)) // ms
+ , Bitrate_(8 * frameSize * 44100.0 / 1024.0)
+ , Timestamp_(0)
+ , FrameNum_(0)
+ {
+ WriteRMF(File_);
+ WritePROP(frameSize, numFrames);
+ WriteMDPR(frameSize, numFrames, numChannels, jointStereo);
+ DataHeaderPos_ = ftell(File_); // Store position of data header, to update it at finish
+ WriteDATA(File_, numFrames);
+ }
+
+ ~TRm() {
+ // TODO: change ICompressedOutput iface to remove this logic from dtor.
+ int64_t endDataPos = ftell(File_);
+ int64_t dataChunkSz = endDataPos - DataHeaderPos_;
+ if (dataChunkSz <= 0xffffffff) {
+ if (fseek(File_, DataHeaderPos_ + 4, SEEK_SET) == 0) {
+ char tmp[sizeof(uint32_t)];
+ *reinterpret_cast<uint32_t*>(&tmp[0]) = swapbyte32_on_le(dataChunkSz);
+ if (fwrite(tmp, sizeof(uint32_t), 1, File_) != 1) {
+ fprintf(stderr, "Unable to update data chink size");
+ }
+ } else {
+ fprintf(stderr, "Unable to navigate to data chink header");
+ }
+ } else {
+ fprintf(stderr, "Too many data for RM container. Encoded data is writen, but format is incorrect.");
+ }
+
+ fclose(File_);
+ }
+
+ void WriteFrame(std::vector<char> data) override {
+ static std::vector<char> tmp(data.size());
+ scramble_data(&data[0], &tmp[0], data.size());
+ WriteAudioPacket(tmp);
+ FrameNum_++;
+ }
+
+ std::string GetName() const override {
+ return {};
+ }
+
+ uint8_t GetChannelNum() const override {
+ return 0;
+ }
+
+private:
+ FILE* File_;
+ const double FrameDuration_;
+ const uint32_t Bitrate_;
+ double Timestamp_;
+ uint32_t FrameNum_;
+
+ int64_t DataHeaderPos_;
+
+ void WriteAudioPacket(const std::vector<char>& data) {
+ switch (FrameNum_ % 3) {
+ case 0: {
+ char buf[12];
+ *reinterpret_cast<uint16_t*>(buf + 0) = swapbyte16_on_le(0); //packet version
+ *reinterpret_cast<uint16_t*>(buf + 2) = swapbyte16_on_le(3 * data.size() + 12); //packet size
+ *reinterpret_cast<uint16_t*>(buf + 4) = swapbyte16_on_le(0); //stream number
+ *reinterpret_cast<uint32_t*>(buf + 6) = swapbyte32_on_le((uint32_t)Timestamp_); //timestamp
+ buf[10] = 0;
+ buf[11] = 0x02; // This flag and Timestamp calculation are important to play file by original RA player
+
+ if (fwrite(buf, 12, 1, File_) != 1)
+ throw std::runtime_error("Can't write packet header");//, errno);
+ }
+ break;
+ case 2:
+ Timestamp_ += (FrameDuration_ * 3.0);
+ break;
+ }
+ if (fwrite(&data[0], data.size(), 1, File_) != 1)
+ throw std::runtime_error("Can`t write codec data");
+ }
+
+ void WritePROP(uint32_t frameSize, uint32_t numFrames) {
+ constexpr size_t PROP_HEADER_SZ = 50;
+ char buf[PROP_HEADER_SZ] = {
+ 'P', 'R', 'O', 'P'};
+ *reinterpret_cast<uint32_t*>(buf + 4) = swapbyte32_on_le(50);
+ *reinterpret_cast<uint16_t*>(buf + 8) = 0; //chunk version
+ *reinterpret_cast<uint32_t*>(buf + 10) = swapbyte32_on_le(Bitrate_); // max bitrate
+ *reinterpret_cast<uint32_t*>(buf + 14) = swapbyte32_on_le(Bitrate_); // avg bitrate
+ *reinterpret_cast<uint32_t*>(buf + 18) = swapbyte32_on_le(frameSize); // max packet size
+ *reinterpret_cast<uint32_t*>(buf + 22) = swapbyte32_on_le(frameSize); // avg packet size
+ *reinterpret_cast<uint32_t*>(buf + 26) = swapbyte32_on_le(numFrames); // nb packets
+ *reinterpret_cast<uint32_t*>(buf + 30) = swapbyte32_on_le((uint32_t)(numFrames * FrameDuration_)); // duration, ms, FFmpeg use this duration
+ *reinterpret_cast<uint32_t*>(buf + 34) = 0; // preroll
+ *reinterpret_cast<uint32_t*>(buf + 38) = 0; // index chunk offset
+ *reinterpret_cast<uint32_t*>(buf + 42) = swapbyte32_on_le(RMF_HEADER_SZ + PROP_HEADER_SZ + MDPR_HEADER_SZ); // data chunk offset
+ *reinterpret_cast<uint16_t*>(buf + 46) = swapbyte16_on_le(1); // nb streams
+ *reinterpret_cast<uint16_t*>(buf + 48) = swapbyte16_on_le(1 | 2); // flags
+ if (fwrite(buf, PROP_HEADER_SZ, 1, File_) != 1)
+ throw std::runtime_error("Can't write PROP header");//, errno);
+ }
+
+ void WriteMDPR(uint32_t frameSize, uint32_t numFrames, uint8_t numChannels, bool jointStereo) {
+ char buf[MDPR_HEADER_SZ] = {
+ 'M', 'D', 'P', 'R'};
+ *reinterpret_cast<uint32_t*>(buf + 4) = swapbyte32_on_le(MDPR_HEADER_SZ);
+ *reinterpret_cast<uint16_t*>(buf + 8) = 0; //chunk version
+ *reinterpret_cast<uint16_t*>(buf + 10) = 0; //stream id
+ *reinterpret_cast<uint32_t*>(buf + 12) = swapbyte32_on_le(Bitrate_); //max bitrate
+ *reinterpret_cast<uint32_t*>(buf + 16) = swapbyte32_on_le(Bitrate_); //avg bitrate
+ *reinterpret_cast<uint32_t*>(buf + 20) = swapbyte32_on_le(frameSize); //max packet size
+ *reinterpret_cast<uint32_t*>(buf + 24) = swapbyte32_on_le(frameSize); //avg packet size
+ *reinterpret_cast<uint32_t*>(buf + 28) = swapbyte32_on_le(0); //start time
+ *reinterpret_cast<uint32_t*>(buf + 32) = 0; //preroll
+ *reinterpret_cast<uint32_t*>(buf + 36) = swapbyte32_on_le(uint32_t(numFrames * FrameDuration_)); //duration, ms, RA player use this duration
+ *reinterpret_cast<uint8_t*>(buf + 40) = sizeof(RA_DESC); //stream desc len
+ memcpy(buf + 41, RA_DESC, sizeof(RA_DESC));
+ *reinterpret_cast<uint8_t*>(buf + 41 + sizeof(RA_DESC)) = sizeof(RA_MIME); //stream mime type len
+ memcpy(buf + 42 + sizeof(RA_DESC), RA_MIME, sizeof(RA_MIME));
+
+ FillCodecData(buf + 42 + sizeof(RA_MIME) + sizeof(RA_DESC), frameSize, numChannels, jointStereo, Bitrate_);
+
+ if (fwrite(buf, MDPR_HEADER_SZ, 1, File_) != 1)
+ throw std::runtime_error("Can't write MDPR header");//, errno);
+ }
+};
+
+TCompressedOutputPtr CreateRmOutput(const std::string& filename, const std::string& title, uint8_t numChannel,
+ uint32_t numFrames, uint32_t framesize, bool jointStereo) {
+ return std::unique_ptr<TRm>(new TRm(filename, title, numChannel, numFrames, framesize, jointStereo));
+}
diff --git a/src/rm.h b/src/rm.h
new file mode 100644
index 0000000..1a1e512
--- /dev/null
+++ b/src/rm.h
@@ -0,0 +1,24 @@
+/*
+ * This file is part of AtracDEnc.
+ *
+ * AtracDEnc is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * AtracDEnc is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with AtracDEnc; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "compressed_io.h"
+
+TCompressedOutputPtr CreateRmOutput(const std::string& filename, const std::string& title, uint8_t numChannel,
+ uint32_t numFrames, uint32_t framesize, bool jointStereo);