diff options
author | ghost <c42723f8913e7023435c322995e52208d7cf860640ca75118f526cca77cb5bab@yggmail> | 2022-01-05 00:35:26 +0300 |
---|---|---|
committer | Daniil Cherednik <dan.cherednik@gmail.com> | 2022-02-20 01:05:40 +0300 |
commit | a8690dd6b204eae3a5e8d8466aa2abab73cdad48 (patch) | |
tree | 1d71801a076f9d5f1fe549132c920a71b36a29d5 | |
parent | cf8f27830d19e5ec2524a9dc6b50c8a30869917f (diff) | |
download | atracdenc-a8690dd6b204eae3a5e8d8466aa2abab73cdad48.tar.gz |
Support for RealMedia output file format.
ATRAC3 is one of codecs used for RealMedia player.
-rw-r--r-- | src/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/atrac/atrac3_bitstream.h | 4 | ||||
-rw-r--r-- | src/atrac3denc.cpp | 8 | ||||
-rw-r--r-- | src/main.cpp | 37 | ||||
-rw-r--r-- | src/rm.cpp | 282 | ||||
-rw-r--r-- | src/rm.h | 24 |
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); |