mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2026-01-29 13:50:48 +03:00
update cspot
This commit is contained in:
BIN
components/spotify/cspot/bell/src/.DS_Store
vendored
BIN
components/spotify/cspot/bell/src/.DS_Store
vendored
Binary file not shown.
@@ -1,88 +0,0 @@
|
||||
#include "BaseHTTPServer.h"
|
||||
#include <sstream>
|
||||
|
||||
unsigned char bell::BaseHTTPServer::h2int(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
return ((unsigned char)c - '0');
|
||||
}
|
||||
if (c >= 'a' && c <= 'f')
|
||||
{
|
||||
return ((unsigned char)c - 'a' + 10);
|
||||
}
|
||||
if (c >= 'A' && c <= 'F')
|
||||
{
|
||||
return ((unsigned char)c - 'A' + 10);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
std::string bell::BaseHTTPServer::urlDecode(std::string str)
|
||||
{
|
||||
std::string encodedString = "";
|
||||
char c;
|
||||
char code0;
|
||||
char code1;
|
||||
for (int i = 0; i < str.length(); i++)
|
||||
{
|
||||
c = str[i];
|
||||
if (c == '+')
|
||||
{
|
||||
encodedString += ' ';
|
||||
}
|
||||
else if (c == '%')
|
||||
{
|
||||
i++;
|
||||
code0 = str[i];
|
||||
i++;
|
||||
code1 = str[i];
|
||||
c = (h2int(code0) << 4) | h2int(code1);
|
||||
encodedString += c;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
encodedString += c;
|
||||
}
|
||||
}
|
||||
|
||||
return encodedString;
|
||||
}
|
||||
|
||||
std::vector<std::string> bell::BaseHTTPServer::splitUrl(const std::string &url, char delimiter)
|
||||
{
|
||||
std::stringstream ssb(url);
|
||||
std::string segment;
|
||||
std::vector<std::string> seglist;
|
||||
|
||||
while (std::getline(ssb, segment, delimiter))
|
||||
{
|
||||
seglist.push_back(segment);
|
||||
}
|
||||
return seglist;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> bell::BaseHTTPServer::parseQueryString(const std::string &queryString)
|
||||
{
|
||||
std::map<std::string, std::string> query;
|
||||
auto prefixedString = "&" + queryString;
|
||||
while (prefixedString.find("&") != std::string::npos)
|
||||
{
|
||||
auto keyStart = prefixedString.find("&");
|
||||
auto keyEnd = prefixedString.find("=");
|
||||
// Find second occurence of "&" in prefixedString
|
||||
auto valueEnd = prefixedString.find("&", keyStart + 1);
|
||||
if (valueEnd == std::string::npos)
|
||||
{
|
||||
valueEnd = prefixedString.size();
|
||||
}
|
||||
|
||||
auto key = prefixedString.substr(keyStart + 1, keyEnd - 1);
|
||||
auto value = prefixedString.substr(keyEnd + 1, valueEnd - keyEnd - 1);
|
||||
query[key] = urlDecode(value);
|
||||
prefixedString = prefixedString.substr(valueEnd);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "BellLogger.h"
|
||||
|
||||
std::shared_ptr<bell::AbstractLogger> bell::bellGlobalLogger;
|
||||
bell::AbstractLogger* bell::bellGlobalLogger;
|
||||
|
||||
void bell::setDefaultLogger() {
|
||||
bell::bellGlobalLogger = std::make_shared<bell::BellLogger>();
|
||||
bell::bellGlobalLogger = new bell::BellLogger();
|
||||
}
|
||||
|
||||
void bell::enableSubmoduleLogging() {
|
||||
|
||||
@@ -18,8 +18,8 @@ void bell::BinaryReader::close() {
|
||||
}
|
||||
|
||||
void bell::BinaryReader::skip(size_t pos) {
|
||||
uint8_t b[pos];
|
||||
stream->read((uint8_t *)b, pos);
|
||||
std::vector<uint8_t> b(pos);
|
||||
stream->read(&b[0], pos);
|
||||
}
|
||||
|
||||
int32_t bell::BinaryReader::readInt() {
|
||||
|
||||
174
components/spotify/cspot/bell/src/BufferedStream.cpp
Normal file
174
components/spotify/cspot/bell/src/BufferedStream.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-7.
|
||||
|
||||
#include "BufferedStream.h"
|
||||
#include <cstring>
|
||||
|
||||
BufferedStream::BufferedStream(
|
||||
const std::string &taskName,
|
||||
uint32_t bufferSize,
|
||||
uint32_t readThreshold,
|
||||
uint32_t readSize,
|
||||
uint32_t readyThreshold,
|
||||
uint32_t notReadyThreshold,
|
||||
bool waitForReady)
|
||||
: bell::Task(taskName, 4096, 5, 0) {
|
||||
this->bufferSize = bufferSize;
|
||||
this->readAt = bufferSize - readThreshold;
|
||||
this->readSize = readSize;
|
||||
this->readyThreshold = readyThreshold;
|
||||
this->notReadyThreshold = notReadyThreshold;
|
||||
this->waitForReady = waitForReady;
|
||||
this->buf = static_cast<uint8_t *>(malloc(bufferSize));
|
||||
this->bufEnd = buf + bufferSize;
|
||||
reset();
|
||||
}
|
||||
|
||||
BufferedStream::~BufferedStream() {
|
||||
this->close();
|
||||
free(buf);
|
||||
}
|
||||
|
||||
void BufferedStream::close() {
|
||||
this->terminate = true;
|
||||
this->readSem.give(); // force a read operation
|
||||
const std::lock_guard lock(runningMutex);
|
||||
if (this->source)
|
||||
this->source->close();
|
||||
this->source = nullptr;
|
||||
}
|
||||
|
||||
void BufferedStream::reset() {
|
||||
this->bufReadPtr = this->buf;
|
||||
this->bufWritePtr = this->buf;
|
||||
this->readTotal = 0;
|
||||
this->bufferTotal = 0;
|
||||
this->readAvailable = 0;
|
||||
this->terminate = false;
|
||||
}
|
||||
|
||||
bool BufferedStream::open(const std::shared_ptr<bell::ByteStream> &stream) {
|
||||
if (this->running)
|
||||
this->close();
|
||||
reset();
|
||||
this->source = stream;
|
||||
startTask();
|
||||
return source.get();
|
||||
}
|
||||
|
||||
bool BufferedStream::open(const StreamReader &newReader, uint32_t initialOffset) {
|
||||
if (this->running)
|
||||
this->close();
|
||||
reset();
|
||||
this->reader = newReader;
|
||||
this->bufferTotal = initialOffset;
|
||||
startTask();
|
||||
return source.get();
|
||||
}
|
||||
|
||||
bool BufferedStream::isReady() const {
|
||||
return readAvailable >= readyThreshold;
|
||||
}
|
||||
|
||||
bool BufferedStream::isNotReady() const {
|
||||
return readAvailable < notReadyThreshold;
|
||||
}
|
||||
|
||||
size_t BufferedStream::skip(size_t len) {
|
||||
return read(nullptr, len);
|
||||
}
|
||||
|
||||
size_t BufferedStream::position() {
|
||||
return readTotal;
|
||||
}
|
||||
|
||||
size_t BufferedStream::size() {
|
||||
return source->size();
|
||||
}
|
||||
|
||||
uint32_t BufferedStream::lengthBetween(uint8_t *me, uint8_t *other) {
|
||||
const std::lock_guard lock(readMutex);
|
||||
if (other <= me) {
|
||||
// buf .... other ...... me ........ bufEnd
|
||||
// buf .... me/other ........ bufEnd
|
||||
return bufEnd - me;
|
||||
} else {
|
||||
// buf ........ me ........ other .... bufEnd
|
||||
return other - me;
|
||||
}
|
||||
}
|
||||
|
||||
size_t BufferedStream::read(uint8_t *dst, size_t len) {
|
||||
if (waitForReady && isNotReady()) {
|
||||
while ((source || reader) && !isReady()) {} // end waiting after termination
|
||||
}
|
||||
if (!running && !readAvailable) {
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
uint32_t read = 0;
|
||||
uint32_t toReadTotal = std::min(readAvailable.load(), static_cast<uint32_t>(len));
|
||||
while (toReadTotal > 0) {
|
||||
uint32_t toRead = std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr));
|
||||
if (dst) {
|
||||
memcpy(dst, bufReadPtr, toRead);
|
||||
dst += toRead;
|
||||
}
|
||||
readAvailable -= toRead;
|
||||
bufReadPtr += toRead;
|
||||
if (bufReadPtr >= bufEnd)
|
||||
bufReadPtr = buf;
|
||||
toReadTotal -= toRead;
|
||||
read += toRead;
|
||||
readTotal += toRead;
|
||||
}
|
||||
this->readSem.give();
|
||||
return read;
|
||||
}
|
||||
|
||||
void BufferedStream::runTask() {
|
||||
const std::lock_guard lock(runningMutex);
|
||||
running = true;
|
||||
if (!source && reader) {
|
||||
// get the initial request on the task's thread
|
||||
source = reader(this->bufferTotal);
|
||||
}
|
||||
while (!terminate) {
|
||||
if (!source)
|
||||
break;
|
||||
if (isReady()) {
|
||||
// buffer ready, wait for any read operations
|
||||
this->readSem.wait();
|
||||
}
|
||||
if (terminate)
|
||||
break;
|
||||
if (readAvailable > readAt)
|
||||
continue;
|
||||
// here, the buffer needs re-filling
|
||||
uint32_t len;
|
||||
bool wasReady = isReady();
|
||||
do {
|
||||
uint32_t toRead = std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr));
|
||||
if (!source) {
|
||||
len = 0;
|
||||
break;
|
||||
}
|
||||
len = source->read(bufWritePtr, toRead);
|
||||
readAvailable += len;
|
||||
bufferTotal += len;
|
||||
bufWritePtr += len;
|
||||
if (bufWritePtr >= bufEnd) // TODO is == enough here?
|
||||
bufWritePtr = buf;
|
||||
} while (len && readSize < bufferSize - readAvailable); // loop until there's no more free space in the buffer
|
||||
if (!len && reader)
|
||||
source = reader(bufferTotal);
|
||||
else if (!len)
|
||||
terminate = true;
|
||||
// signal that buffer is ready for reading
|
||||
if (!wasReady && isReady()) {
|
||||
this->readySem.give();
|
||||
}
|
||||
}
|
||||
source = nullptr;
|
||||
reader = nullptr;
|
||||
running = false;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
#ifdef BELL_USE_MBEDTLS
|
||||
#include "CryptoMbedTLS.h"
|
||||
#include "Crypto.h"
|
||||
|
||||
CryptoMbedTLS::CryptoMbedTLS()
|
||||
{
|
||||
@@ -224,4 +223,3 @@ std::vector<uint8_t> CryptoMbedTLS::generateVectorWithRandomData(size_t length)
|
||||
|
||||
return randomVector;
|
||||
}
|
||||
#endif
|
||||
@@ -1,184 +0,0 @@
|
||||
#include "CryptoOpenSSL.h"
|
||||
namespace
|
||||
{
|
||||
struct BIOFreeAll
|
||||
{
|
||||
void operator()(BIO *p) { BIO_free_all(p); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
CryptoOpenSSL::CryptoOpenSSL()
|
||||
{
|
||||
// OpenSSL init
|
||||
ENGINE_load_builtin_engines();
|
||||
ENGINE_register_all_complete();
|
||||
this->publicKey = std::vector<uint8_t>(DH_KEY_SIZE);
|
||||
this->privateKey = generateVectorWithRandomData(DH_KEY_SIZE);
|
||||
}
|
||||
|
||||
CryptoOpenSSL::~CryptoOpenSSL()
|
||||
{
|
||||
if (this->dhContext != nullptr)
|
||||
{
|
||||
DH_free(this->dhContext);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> CryptoOpenSSL::base64Decode(const std::string& data)
|
||||
{
|
||||
// base64 in openssl is an absolute mess lmao
|
||||
std::unique_ptr<BIO, BIOFreeAll> b64(BIO_new(BIO_f_base64()));
|
||||
BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
|
||||
BIO *source = BIO_new_mem_buf(data.c_str(), -1); // read-only source
|
||||
BIO_push(b64.get(), source);
|
||||
const int maxlen = data.size() / 4 * 3 + 1;
|
||||
std::vector<uint8_t> decoded(maxlen);
|
||||
const int len = BIO_read(b64.get(), decoded.data(), maxlen);
|
||||
decoded.resize(len);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
std::string CryptoOpenSSL::base64Encode(const std::vector<uint8_t>& data)
|
||||
{
|
||||
// base64 in openssl is an absolute mess lmao x 2
|
||||
std::unique_ptr<BIO, BIOFreeAll> b64(BIO_new(BIO_f_base64()));
|
||||
|
||||
// No newline mode, put all the data into sink
|
||||
BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
|
||||
BIO *sink = BIO_new(BIO_s_mem());
|
||||
BIO_push(b64.get(), sink);
|
||||
BIO_write(b64.get(), data.data(), data.size());
|
||||
BIO_flush(b64.get());
|
||||
const char *encoded;
|
||||
const long len = BIO_get_mem_data(sink, &encoded);
|
||||
return std::string(encoded, len);
|
||||
}
|
||||
|
||||
// Sha1
|
||||
void CryptoOpenSSL::sha1Init()
|
||||
{
|
||||
SHA1_Init(&sha1Context);
|
||||
}
|
||||
|
||||
void CryptoOpenSSL::sha1Update(const std::string& s)
|
||||
{
|
||||
sha1Update(std::vector<uint8_t>(s.begin(), s.end()));
|
||||
}
|
||||
void CryptoOpenSSL::sha1Update(const std::vector<uint8_t>& vec)
|
||||
{
|
||||
SHA1_Update(&sha1Context, vec.data(), vec.size());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> CryptoOpenSSL::sha1FinalBytes()
|
||||
{
|
||||
std::vector<uint8_t> digest(20); // 20 is 160 bits
|
||||
SHA1_Final(digest.data(), &sha1Context);
|
||||
return digest;
|
||||
}
|
||||
|
||||
std::string CryptoOpenSSL::sha1Final()
|
||||
{
|
||||
auto digest = sha1FinalBytes();
|
||||
return std::string(digest.begin(), digest.end());
|
||||
}
|
||||
|
||||
// HMAC SHA1
|
||||
std::vector<uint8_t> CryptoOpenSSL::sha1HMAC(const std::vector<uint8_t>& inputKey, const std::vector<uint8_t>& message)
|
||||
{
|
||||
std::vector<uint8_t> digest(20); // 20 is 160 bits
|
||||
auto hmacContext = HMAC_CTX_new();
|
||||
HMAC_Init_ex(hmacContext, inputKey.data(), inputKey.size(), EVP_sha1(), NULL);
|
||||
HMAC_Update(hmacContext, message.data(), message.size());
|
||||
|
||||
unsigned int resLen = 0;
|
||||
HMAC_Final(hmacContext, digest.data(), &resLen);
|
||||
|
||||
HMAC_CTX_free(hmacContext);
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
// AES CTR
|
||||
void CryptoOpenSSL::aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, uint8_t* buffer, size_t nbytes)
|
||||
{
|
||||
// Prepare AES_KEY
|
||||
auto cryptoKey = AES_KEY();
|
||||
AES_set_encrypt_key(key.data(), 128, &cryptoKey);
|
||||
|
||||
// Needed for openssl internal cache
|
||||
unsigned char ecountBuf[16] = {0};
|
||||
unsigned int offsetInBlock = 0;
|
||||
|
||||
CRYPTO_ctr128_encrypt(
|
||||
buffer,
|
||||
buffer,
|
||||
nbytes,
|
||||
&cryptoKey,
|
||||
iv.data(),
|
||||
ecountBuf,
|
||||
&offsetInBlock,
|
||||
block128_f(AES_encrypt));
|
||||
}
|
||||
|
||||
void CryptoOpenSSL::aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& data)
|
||||
{
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
EVP_CIPHER_CTX_init(ctx);
|
||||
int len = 0;
|
||||
|
||||
EVP_DecryptInit_ex(ctx, EVP_aes_192_ecb(), NULL, key.data(), NULL);
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 0); // disable padding
|
||||
EVP_DecryptUpdate(ctx, data.data(), &len, data.data(), data.size());
|
||||
EVP_DecryptFinal_ex(ctx, data.data() + len, &len);
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
|
||||
// PBKDF2
|
||||
std::vector<uint8_t> CryptoOpenSSL::pbkdf2HmacSha1(const std::vector<uint8_t>& password, const std::vector<uint8_t>& salt, int iterations, int digestSize)
|
||||
{
|
||||
std::vector<uint8_t> digest(digestSize);
|
||||
|
||||
// Generate PKDF2 digest
|
||||
PKCS5_PBKDF2_HMAC_SHA1((const char *)password.data(), password.size(),
|
||||
(const unsigned char *)salt.data(), salt.size(), iterations,
|
||||
digestSize, digest.data());
|
||||
return digest;
|
||||
}
|
||||
|
||||
void CryptoOpenSSL::dhInit()
|
||||
{
|
||||
// Free old context
|
||||
if (this->dhContext != nullptr)
|
||||
{
|
||||
DH_free(this->dhContext);
|
||||
}
|
||||
this->dhContext = DH_new();
|
||||
|
||||
// Set prime and the generator
|
||||
DH_set0_pqg(this->dhContext, BN_bin2bn(DHPrime, DH_KEY_SIZE, NULL), NULL, BN_bin2bn(DHGenerator, 1, NULL));
|
||||
|
||||
// Generate public and private keys and copy them to vectors
|
||||
DH_generate_key(this->dhContext);
|
||||
BN_bn2bin(DH_get0_pub_key(dhContext), this->publicKey.data());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> CryptoOpenSSL::dhCalculateShared(const std::vector<uint8_t>& remoteKey)
|
||||
{
|
||||
auto sharedKey = std::vector<uint8_t>(DH_KEY_SIZE);
|
||||
// Convert remote key to bignum and compute shared key
|
||||
auto pubKey = BN_bin2bn(&remoteKey[0], DH_KEY_SIZE, NULL);
|
||||
DH_compute_key(sharedKey.data(), pubKey, this->dhContext);
|
||||
BN_free(pubKey);
|
||||
return sharedKey;
|
||||
}
|
||||
|
||||
// Random stuff
|
||||
std::vector<uint8_t> CryptoOpenSSL::generateVectorWithRandomData(size_t length)
|
||||
{
|
||||
std::vector<uint8_t> randomVec(length);
|
||||
if(RAND_bytes(randomVec.data(), length) == 0)
|
||||
{
|
||||
}
|
||||
return randomVec;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#include "DecoderGlobals.h"
|
||||
|
||||
std::shared_ptr<bell::DecodersInstance> bell::decodersInstance;
|
||||
|
||||
void bell::createDecoders()
|
||||
{
|
||||
bell::decodersInstance = std::make_shared<bell::DecodersInstance>();
|
||||
}
|
||||
@@ -55,14 +55,14 @@ std::vector<std::string> bell::HTTPServer::splitUrl(const std::string &url,
|
||||
|
||||
void bell::HTTPServer::registerHandler(RequestType requestType,
|
||||
const std::string &routeUrl,
|
||||
httpHandler handler) {
|
||||
httpHandler handler,
|
||||
bool readBodyToStr) {
|
||||
if (routes.find(routeUrl) == routes.end()) {
|
||||
routes.insert({routeUrl, std::vector<HTTPRoute>()});
|
||||
}
|
||||
this->routes[routeUrl].push_back(HTTPRoute{
|
||||
.requestType = requestType,
|
||||
.handler = handler,
|
||||
});
|
||||
this->routes[routeUrl].push_back(HTTPRoute{.requestType = requestType,
|
||||
.handler = handler,
|
||||
.readBodyToStr = readBodyToStr});
|
||||
}
|
||||
|
||||
void bell::HTTPServer::listen() {
|
||||
@@ -126,8 +126,8 @@ void bell::HTTPServer::listen() {
|
||||
HTTPConnection conn = { .buffer = std::vector<uint8_t>(128),
|
||||
.httpMethod = "" };
|
||||
|
||||
this->connections.insert({ newFd, conn });
|
||||
}
|
||||
this->connections.insert({newFd, conn});
|
||||
}
|
||||
|
||||
/* Service other sockets and update set & max */
|
||||
maxfd = sockfd;
|
||||
@@ -140,8 +140,7 @@ void bell::HTTPServer::listen() {
|
||||
FD_CLR(fd, &activeFdSet);
|
||||
this->connections.erase(
|
||||
it++); // or "it = m.erase(it)" since C++11
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (fd != sockfd && FD_ISSET(fd, &readFdSet)) {
|
||||
/* Data arriving on an already-connected socket. */
|
||||
readFromClient(fd);
|
||||
@@ -156,6 +155,10 @@ void bell::HTTPServer::listen() {
|
||||
|
||||
void bell::HTTPServer::readFromClient(int clientFd) {
|
||||
HTTPConnection &conn = this->connections[clientFd];
|
||||
if (conn.headersRead) {
|
||||
return;
|
||||
}
|
||||
conn.fd = clientFd;
|
||||
|
||||
int nbytes = recv(clientFd, (char*) &conn.buffer[0], conn.buffer.size(), 0);
|
||||
if (nbytes < 0) {
|
||||
@@ -165,49 +168,55 @@ void bell::HTTPServer::readFromClient(int clientFd) {
|
||||
} else if (nbytes == 0) {
|
||||
this->closeConnection(clientFd);
|
||||
} else {
|
||||
conn.currentLine +=
|
||||
std::string(conn.buffer.data(), conn.buffer.data() + nbytes);
|
||||
READBODY:
|
||||
if (!conn.isReadingBody) {
|
||||
while (conn.currentLine.find("\r\n") != std::string::npos) {
|
||||
auto line =
|
||||
conn.currentLine.substr(0, conn.currentLine.find("\r\n"));
|
||||
conn.currentLine = conn.currentLine.substr(
|
||||
conn.currentLine.find("\r\n") + 2, conn.currentLine.size());
|
||||
if (line.find("GET ") != std::string::npos ||
|
||||
line.find("POST ") != std::string::npos ||
|
||||
line.find("OPTIONS ") != std::string::npos) {
|
||||
conn.httpMethod = line;
|
||||
}
|
||||
// append buffer to partialBuffer
|
||||
conn.partialBuffer.insert(conn.partialBuffer.end(), conn.buffer.begin(),
|
||||
conn.buffer.begin() + nbytes);
|
||||
auto stringifiedBuffer =
|
||||
std::string(conn.partialBuffer.data(),
|
||||
conn.partialBuffer.data() + conn.partialBuffer.size());
|
||||
|
||||
if (line.find("Content-Length: ") != std::string::npos) {
|
||||
conn.contentLength =
|
||||
std::stoi(line.substr(16, line.size() - 1));
|
||||
}
|
||||
// detect hostname for captive portal
|
||||
if (line.find("Host: connectivitycheck.gstatic.com") !=
|
||||
std::string::npos) {
|
||||
conn.isCaptivePortal = true;
|
||||
BELL_LOG(info, "http", "Captive portal request detected");
|
||||
}
|
||||
if (line.size() == 0) {
|
||||
if (conn.contentLength != 0) {
|
||||
conn.isReadingBody = true;
|
||||
goto READBODY;
|
||||
READBODY:
|
||||
auto readSize = 0;
|
||||
|
||||
while (stringifiedBuffer.find("\r\n") != std::string::npos) {
|
||||
auto line =
|
||||
stringifiedBuffer.substr(0, stringifiedBuffer.find("\r\n"));
|
||||
readSize += stringifiedBuffer.find("\r\n") + 2;
|
||||
stringifiedBuffer = stringifiedBuffer.substr(
|
||||
stringifiedBuffer.find("\r\n") + 2, stringifiedBuffer.size());
|
||||
if (line.find("GET ") != std::string::npos ||
|
||||
line.find("POST ") != std::string::npos ||
|
||||
line.find("OPTIONS ") != std::string::npos) {
|
||||
conn.httpMethod = line;
|
||||
}
|
||||
|
||||
if (line.find("Content-Length: ") != std::string::npos) {
|
||||
conn.contentLength =
|
||||
std::stoi(line.substr(16, line.size() - 1));
|
||||
}
|
||||
// detect hostname for captive portal
|
||||
if (line.find("Host: connectivitycheck.gstatic.com") !=
|
||||
std::string::npos) {
|
||||
conn.isCaptivePortal = true;
|
||||
BELL_LOG(info, "http", "Captive portal request detected");
|
||||
}
|
||||
if (line.size() == 0) {
|
||||
if (conn.contentLength != 0) {
|
||||
// conn.isReadingBody = true;
|
||||
// remove readSize bytes from partialBuffer
|
||||
conn.partialBuffer.erase(conn.partialBuffer.begin(),
|
||||
conn.partialBuffer.begin() +
|
||||
readSize);
|
||||
findAndHandleRoute(conn);
|
||||
// goto READBODY;
|
||||
} else {
|
||||
if (!conn.isCaptivePortal) {
|
||||
findAndHandleRoute(conn);
|
||||
} else {
|
||||
if (!conn.isCaptivePortal) {
|
||||
findAndHandleRoute(conn.httpMethod,
|
||||
conn.currentLine, clientFd);
|
||||
} else {
|
||||
this->redirectCaptivePortal(clientFd);
|
||||
}
|
||||
this->redirectCaptivePortal(clientFd);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (conn.currentLine.size() >= conn.contentLength) {
|
||||
findAndHandleRoute(conn.httpMethod, conn.currentLine, clientFd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,8 +376,11 @@ bell::HTTPServer::parseQueryString(const std::string &queryString) {
|
||||
return query;
|
||||
}
|
||||
|
||||
void bell::HTTPServer::findAndHandleRoute(std::string &url, std::string &body,
|
||||
int connectionFd) {
|
||||
void bell::HTTPServer::findAndHandleRoute(HTTPConnection &conn) {
|
||||
conn.headersRead = true;
|
||||
auto connectionFd = conn.fd;
|
||||
// auto body = conn.partialBuffer;
|
||||
auto url = conn.httpMethod;
|
||||
std::map<std::string, std::string> pathParams;
|
||||
std::map<std::string, std::string> queryParams;
|
||||
|
||||
@@ -451,18 +463,36 @@ void bell::HTTPServer::findAndHandleRoute(std::string &url, std::string &body,
|
||||
}
|
||||
|
||||
if (matches) {
|
||||
if (body.find('&') != std::string::npos) {
|
||||
queryParams = this->parseQueryString(body);
|
||||
auto reader = std::make_unique<RequestBodyReader>(
|
||||
conn.contentLength, conn.fd, conn.partialBuffer);
|
||||
|
||||
auto body = std::string();
|
||||
if (route.readBodyToStr) {
|
||||
body.resize(conn.contentLength);
|
||||
auto read = 0;
|
||||
while (read < conn.contentLength) {
|
||||
auto readBytes = reader->read(
|
||||
body.data() + read, conn.contentLength - read);
|
||||
read += readBytes;
|
||||
}
|
||||
body.resize(read);
|
||||
if (body.find('&') != std::string::npos) {
|
||||
queryParams = this->parseQueryString(body);
|
||||
}
|
||||
}
|
||||
|
||||
HTTPRequest req = {.urlParams = pathParams,
|
||||
.queryParams = queryParams,
|
||||
.body = body,
|
||||
.url = path,
|
||||
.handlerId = 0,
|
||||
.connection = connectionFd};
|
||||
std::unique_ptr<HTTPRequest> req =
|
||||
std::make_unique<HTTPRequest>();
|
||||
req->queryParams = queryParams;
|
||||
req->urlParams = pathParams;
|
||||
req->url = path;
|
||||
req->body = body;
|
||||
req->connection = connectionFd;
|
||||
req->handlerId = 0;
|
||||
req->responseReader = std::move(reader);
|
||||
req->contentLength = conn.contentLength;
|
||||
|
||||
route.handler(req);
|
||||
route.handler(std::move(req));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,19 @@
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include "win32shim.h"
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/tcp.h>
|
||||
#endif
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
bell::HTTPStream::HTTPStream()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "platform/TLSSocket.h"
|
||||
#include "TLSSocket.h"
|
||||
|
||||
/**
|
||||
* Platform TLSSocket implementation for the mbedtls
|
||||
37
components/spotify/cspot/bell/src/audio/codec/AACDecoder.cpp
Normal file
37
components/spotify/cspot/bell/src/audio/codec/AACDecoder.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-12.
|
||||
|
||||
#include "AACDecoder.h"
|
||||
|
||||
AACDecoder::AACDecoder() {
|
||||
aac = AACInitDecoder();
|
||||
pcmData = (int16_t *)malloc(AAC_MAX_NSAMPS * AAC_MAX_NCHANS * sizeof(int16_t));
|
||||
}
|
||||
|
||||
AACDecoder::~AACDecoder() {
|
||||
AACFreeDecoder(aac);
|
||||
free(pcmData);
|
||||
}
|
||||
|
||||
bool AACDecoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
|
||||
frame.sampRateCore = (int)sampleRate;
|
||||
frame.nChans = channelCount;
|
||||
frame.bitsPerSample = bitDepth;
|
||||
return AACSetRawBlockParams(aac, 0, &frame) == 0;
|
||||
}
|
||||
|
||||
uint8_t *AACDecoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
|
||||
if (!inData)
|
||||
return nullptr;
|
||||
int status = AACDecode(
|
||||
aac,
|
||||
static_cast<unsigned char **>(&inData),
|
||||
reinterpret_cast<int *>(&inLen),
|
||||
static_cast<short *>(this->pcmData));
|
||||
AACGetLastFrameInfo(aac, &frame);
|
||||
if (status != ERR_AAC_NONE) {
|
||||
lastErrno = status;
|
||||
return nullptr;
|
||||
}
|
||||
outLen = frame.outputSamps * sizeof(int16_t);
|
||||
return (uint8_t *)pcmData;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-12.
|
||||
|
||||
#include "ALACDecoder.h"
|
||||
|
||||
ALACDecoder::ALACDecoder() {
|
||||
// aac = AACInitDecoder();
|
||||
// pcmData = (int16_t *)malloc(AAC_MAX_NSAMPS * AAC_MAX_NCHANS * sizeof(int16_t));
|
||||
}
|
||||
|
||||
ALACDecoder::~ALACDecoder() {
|
||||
// AACFreeDecoder(aac);
|
||||
// free(pcmData);
|
||||
}
|
||||
|
||||
bool ALACDecoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
|
||||
// frame.sampRateCore = (int)sampleRate;
|
||||
// frame.nChans = channelCount;
|
||||
// frame.bitsPerSample = bitDepth;
|
||||
// return AACSetRawBlockParams(aac, 0, &frame) == 0;
|
||||
}
|
||||
|
||||
uint8_t *ALACDecoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
|
||||
// if (!inData)
|
||||
// return nullptr;
|
||||
// int status = AACDecode(
|
||||
// aac,
|
||||
// static_cast<unsigned char **>(&inData),
|
||||
// reinterpret_cast<int *>(&inLen),
|
||||
// static_cast<short *>(this->pcmData));
|
||||
// AACGetLastFrameInfo(aac, &frame);
|
||||
// if (status != ERR_AAC_NONE) {
|
||||
// lastErrno = status;
|
||||
// return nullptr;
|
||||
// }
|
||||
// outLen = frame.outputSamps * sizeof(int16_t);
|
||||
// return (uint8_t *)pcmData;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-12.
|
||||
|
||||
#include "AudioCodecs.h"
|
||||
#include <map>
|
||||
|
||||
#ifdef BELL_CODEC_AAC
|
||||
#include "AACDecoder.h"
|
||||
static std::shared_ptr<AACDecoder> codecAac;
|
||||
#endif
|
||||
|
||||
#ifdef BELL_CODEC_MP3
|
||||
#include "MP3Decoder.h"
|
||||
static std::shared_ptr<MP3Decoder> codecMp3;
|
||||
#endif
|
||||
|
||||
#ifdef BELL_CODEC_VORBIS
|
||||
#include "VorbisDecoder.h"
|
||||
static std::shared_ptr<VorbisDecoder> codecVorbis;
|
||||
#endif
|
||||
|
||||
#ifdef BELL_CODEC_OPUS
|
||||
#include "OPUSDecoder.h"
|
||||
static std::shared_ptr<OPUSDecoder> codecOpus;
|
||||
#endif
|
||||
|
||||
std::map<AudioCodec, std::shared_ptr<BaseCodec>> customCodecs;
|
||||
|
||||
std::shared_ptr<BaseCodec> AudioCodecs::getCodec(AudioCodec type) {
|
||||
if (customCodecs.find(type) != customCodecs.end())
|
||||
return customCodecs[type];
|
||||
switch (type) {
|
||||
#ifdef BELL_CODEC_AAC
|
||||
case AudioCodec::AAC:
|
||||
if (codecAac)
|
||||
return codecAac;
|
||||
codecAac = std::make_shared<AACDecoder>();
|
||||
return codecAac;
|
||||
#endif
|
||||
#ifdef BELL_CODEC_MP3
|
||||
case AudioCodec::MP3:
|
||||
if (codecMp3)
|
||||
return codecMp3;
|
||||
codecMp3 = std::make_shared<MP3Decoder>();
|
||||
return codecMp3;
|
||||
#endif
|
||||
#ifdef BELL_CODEC_VORBIS
|
||||
case AudioCodec::VORBIS:
|
||||
if (codecVorbis)
|
||||
return codecVorbis;
|
||||
codecVorbis = std::make_shared<VorbisDecoder>();
|
||||
return codecVorbis;
|
||||
#endif
|
||||
#ifdef BELL_CODEC_OPUS
|
||||
case AudioCodec::OPUS:
|
||||
if (codecOpus)
|
||||
return codecOpus;
|
||||
codecOpus = std::make_shared<OPUSDecoder>();
|
||||
return codecOpus;
|
||||
#endif
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<BaseCodec> AudioCodecs::getCodec(BaseContainer *container) {
|
||||
return getCodec(container->codec);
|
||||
}
|
||||
|
||||
void AudioCodecs::addCodec(AudioCodec type, const std::shared_ptr<BaseCodec> &codec) {
|
||||
customCodecs[type] = codec;
|
||||
}
|
||||
13
components/spotify/cspot/bell/src/audio/codec/BaseCodec.cpp
Normal file
13
components/spotify/cspot/bell/src/audio/codec/BaseCodec.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-12.
|
||||
|
||||
#include "BaseCodec.h"
|
||||
|
||||
bool BaseCodec::setup(BaseContainer *container) {
|
||||
return this->setup(container->sampleRate, container->channelCount, container->bitDepth);
|
||||
}
|
||||
|
||||
uint8_t *BaseCodec::decode(BaseContainer *container, uint32_t &outLen) {
|
||||
uint32_t len;
|
||||
auto *data = container->readSample(len);
|
||||
return decode(data, len, outLen);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "DecoderGlobals.h"
|
||||
|
||||
bell::DecodersInstance* bell::decodersInstance;
|
||||
|
||||
void bell::createDecoders()
|
||||
{
|
||||
bell::decodersInstance = new bell::DecodersInstance();
|
||||
}
|
||||
35
components/spotify/cspot/bell/src/audio/codec/MP3Decoder.cpp
Normal file
35
components/spotify/cspot/bell/src/audio/codec/MP3Decoder.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-14.
|
||||
|
||||
#include "MP3Decoder.h"
|
||||
|
||||
MP3Decoder::MP3Decoder() {
|
||||
mp3 = MP3InitDecoder();
|
||||
pcmData = (int16_t *)malloc(MAX_NSAMP * MAX_NGRAN * MAX_NCHAN * sizeof(int16_t));
|
||||
}
|
||||
|
||||
MP3Decoder::~MP3Decoder() {
|
||||
MP3FreeDecoder(mp3);
|
||||
free(pcmData);
|
||||
}
|
||||
|
||||
bool MP3Decoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t *MP3Decoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
|
||||
if (!inData)
|
||||
return nullptr;
|
||||
int status = MP3Decode(
|
||||
mp3,
|
||||
static_cast<unsigned char **>(&inData),
|
||||
reinterpret_cast<int *>(&inLen),
|
||||
static_cast<short *>(this->pcmData),
|
||||
/* useSize */ 0);
|
||||
MP3GetLastFrameInfo(mp3, &frame);
|
||||
if (status != ERR_MP3_NONE) {
|
||||
lastErrno = status;
|
||||
return nullptr;
|
||||
}
|
||||
outLen = frame.outputSamps * sizeof(int16_t);
|
||||
return (uint8_t *)pcmData;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-14.
|
||||
|
||||
#include "OPUSDecoder.h"
|
||||
#include "opus.h"
|
||||
|
||||
#define MAX_FRAME_SIZE 6 * 960
|
||||
#define MAX_CHANNELS 2
|
||||
|
||||
// dummy structure, just to get access to channels
|
||||
struct OpusDecoder {
|
||||
int dummy1;
|
||||
int dummy2;
|
||||
int channels;
|
||||
};
|
||||
|
||||
OPUSDecoder::OPUSDecoder() {
|
||||
opus = nullptr;
|
||||
pcmData = (int16_t *)malloc(MAX_FRAME_SIZE * MAX_CHANNELS * sizeof(int16_t));
|
||||
}
|
||||
|
||||
OPUSDecoder::~OPUSDecoder() {
|
||||
if (opus)
|
||||
opus_decoder_destroy(opus);
|
||||
free(pcmData);
|
||||
}
|
||||
|
||||
bool OPUSDecoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
|
||||
if (opus)
|
||||
opus_decoder_destroy(opus);
|
||||
opus = opus_decoder_create((int32_t)sampleRate, channelCount, &lastErrno);
|
||||
return !lastErrno;
|
||||
}
|
||||
|
||||
uint8_t *OPUSDecoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
|
||||
if (!inData)
|
||||
return nullptr;
|
||||
outLen = opus_decode(
|
||||
opus,
|
||||
static_cast<unsigned char *>(inData),
|
||||
static_cast<int32_t>(inLen),
|
||||
pcmData,
|
||||
MAX_FRAME_SIZE,
|
||||
false);
|
||||
outLen *= opus->channels * sizeof(int16_t);
|
||||
return (uint8_t *)pcmData;
|
||||
}
|
||||
120
components/spotify/cspot/bell/src/audio/codec/VorbisDecoder.cpp
Normal file
120
components/spotify/cspot/bell/src/audio/codec/VorbisDecoder.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-14.
|
||||
|
||||
#include "VorbisDecoder.h"
|
||||
#include "AudioCodecs.h"
|
||||
|
||||
extern "C" {
|
||||
extern vorbis_dsp_state *vorbis_dsp_create(vorbis_info *vi);
|
||||
extern void vorbis_dsp_destroy(vorbis_dsp_state *v);
|
||||
extern int vorbis_dsp_restart(vorbis_dsp_state *v);
|
||||
extern int vorbis_dsp_headerin(vorbis_info *vi, vorbis_comment *vc, ogg_packet *op);
|
||||
extern int vorbis_dsp_synthesis(vorbis_dsp_state *vd, ogg_packet *op, int decodep);
|
||||
extern int vorbis_dsp_pcmout(vorbis_dsp_state *v, ogg_int16_t *pcm, int samples);
|
||||
extern int vorbis_dsp_read(vorbis_dsp_state *v, int samples);
|
||||
}
|
||||
|
||||
#define VORBIS_BUF_SAMPLES 1024
|
||||
#define VORBIS_BUF_CHANNELS 2
|
||||
|
||||
VorbisDecoder::VorbisDecoder() {
|
||||
vi = new vorbis_info;
|
||||
vorbis_info_init(vi);
|
||||
vc = new vorbis_comment;
|
||||
vorbis_comment_init(vc);
|
||||
|
||||
op.packet = new ogg_reference;
|
||||
op.packet->buffer = new ogg_buffer;
|
||||
op.packet->buffer->refcount = 0;
|
||||
op.packet->buffer->ptr.owner = nullptr;
|
||||
op.packet->buffer->ptr.next = nullptr;
|
||||
op.packet->begin = 0;
|
||||
op.packet->next = nullptr;
|
||||
op.granulepos = -1;
|
||||
op.packetno = 10;
|
||||
|
||||
pcmData = (int16_t *)malloc(VORBIS_BUF_SAMPLES * VORBIS_BUF_CHANNELS * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
VorbisDecoder::~VorbisDecoder() {
|
||||
vorbis_info_clear(vi);
|
||||
vorbis_comment_clear(vc);
|
||||
if (vd)
|
||||
vorbis_dsp_destroy(vd);
|
||||
vd = nullptr;
|
||||
free(pcmData);
|
||||
}
|
||||
|
||||
bool VorbisDecoder::setup(BaseContainer *container) {
|
||||
uint32_t setupLen;
|
||||
uint8_t *setup = container->getSetupData(setupLen, AudioCodec::VORBIS);
|
||||
if (!setup)
|
||||
return false;
|
||||
op.b_o_s = true; // mark this page as beginning of stream
|
||||
uint32_t bytesLeft = setupLen - 1; // minus header count length (8 bit)
|
||||
std::vector<uint32_t> headers(setup[0]);
|
||||
for (uint8_t i = 0; i < setup[0]; i++) {
|
||||
uint8_t *sizeByte = (uint8_t *)setup + 1 + i;
|
||||
headers[i] = 0;
|
||||
while (*sizeByte == 255) {
|
||||
headers[i] += *(sizeByte++);
|
||||
bytesLeft--;
|
||||
}
|
||||
headers[i] += *sizeByte;
|
||||
bytesLeft--;
|
||||
}
|
||||
// parse all headers from the setup data
|
||||
for (const auto &headerSize : headers) {
|
||||
setPacket(setup + setupLen - bytesLeft, headerSize);
|
||||
bytesLeft -= headerSize;
|
||||
lastErrno = vorbis_dsp_headerin(vi, vc, &op);
|
||||
if (lastErrno < 0) {
|
||||
bytesLeft = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// parse last header, not present in header table (seems to happen for MP4 containers)
|
||||
if (bytesLeft) {
|
||||
setPacket(setup + setupLen - bytesLeft, bytesLeft);
|
||||
lastErrno = vorbis_dsp_headerin(vi, vc, &op);
|
||||
}
|
||||
// disable BOS to allow reading audio data
|
||||
op.b_o_s = false;
|
||||
// set up the codec
|
||||
if (vd)
|
||||
vorbis_dsp_restart(vd);
|
||||
else
|
||||
vd = vorbis_dsp_create(vi);
|
||||
return !lastErrno;
|
||||
}
|
||||
|
||||
bool VorbisDecoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
|
||||
// manual setup is not allowed
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *VorbisDecoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
|
||||
if (!inData || !vi)
|
||||
return nullptr;
|
||||
setPacket(inData, inLen);
|
||||
// sources:
|
||||
// - vorbisfile.c:556
|
||||
// - vorbisfile.c:1557
|
||||
lastErrno = vorbis_dsp_synthesis(vd, &op, 1);
|
||||
if (lastErrno < 0)
|
||||
return nullptr;
|
||||
int samples = vorbis_dsp_pcmout(vd, pcmData, VORBIS_BUF_SAMPLES);
|
||||
outLen = samples;
|
||||
if (samples) {
|
||||
if (samples > 0) {
|
||||
vorbis_dsp_read(vd, samples);
|
||||
outLen = samples * 2 * vi->channels;
|
||||
}
|
||||
}
|
||||
return (uint8_t *)pcmData;
|
||||
}
|
||||
|
||||
void VorbisDecoder::setPacket(uint8_t *inData, uint32_t inLen) const {
|
||||
op.packet->buffer->data = static_cast<unsigned char *>(inData);
|
||||
op.packet->buffer->size = static_cast<long>(inLen);
|
||||
op.packet->length = static_cast<long>(inLen);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-15.
|
||||
|
||||
#include "AudioContainers.h"
|
||||
#include "Mpeg4Container.h"
|
||||
#ifdef _WIN32
|
||||
#include "win32shim.h"
|
||||
#endif
|
||||
|
||||
std::unique_ptr<BaseContainer> AudioContainers::create(const char *mimeType) {
|
||||
char *type = strchr((char *)mimeType, '/');
|
||||
if (!type || *(++type) == '\0')
|
||||
return nullptr;
|
||||
|
||||
if (strncasecmp(type, "mp4", 3) == 0)
|
||||
return std::make_unique<Mpeg4Container>();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-7.
|
||||
|
||||
#include "BaseContainer.h"
|
||||
|
||||
void BaseContainer::feed(const std::shared_ptr<bell::ByteStream> &stream, uint32_t position) {
|
||||
this->reader = std::make_unique<bell::BinaryReader>(stream);
|
||||
this->source = stream;
|
||||
this->pos = position;
|
||||
}
|
||||
|
||||
// TODO move source stream reading here, and set closed = true when stream ends
|
||||
|
||||
uint8_t BaseContainer::readUint8() {
|
||||
pos += 1;
|
||||
return reader->readByte();
|
||||
}
|
||||
|
||||
uint16_t BaseContainer::readUint16() {
|
||||
pos += 2;
|
||||
return reader->readShort();
|
||||
}
|
||||
|
||||
uint32_t BaseContainer::readUint24() {
|
||||
uint8_t b[3];
|
||||
readBytes(b, 3);
|
||||
return static_cast<int32_t>((b[2]) | (b[1] << 8) | (b[0] << 16));
|
||||
}
|
||||
|
||||
uint32_t BaseContainer::readUint32() {
|
||||
pos += 4;
|
||||
return reader->readUInt();
|
||||
}
|
||||
|
||||
uint64_t BaseContainer::readUint64() {
|
||||
pos += 8;
|
||||
return reader->readLong();
|
||||
}
|
||||
|
||||
uint32_t BaseContainer::readVarint32() {
|
||||
uint8_t b = readUint8();
|
||||
uint32_t result = b & 0x7f;
|
||||
while (b & 0b10000000) {
|
||||
b = readUint8();
|
||||
result <<= 7;
|
||||
result |= b & 0x7f;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t BaseContainer::readBytes(uint8_t *dst, uint32_t num) {
|
||||
if (!num)
|
||||
return 0;
|
||||
uint32_t len, total = 0;
|
||||
do {
|
||||
if (dst) {
|
||||
len = source->read(dst, num);
|
||||
dst += len; // increment destination pointer
|
||||
} else {
|
||||
len = source->skip(num);
|
||||
}
|
||||
total += len; // increment total read count
|
||||
pos += len; // increment absolute source position
|
||||
num -= len; // decrement bytes left to read
|
||||
} while (len && num);
|
||||
if (!len) // source->read() returned 0, it's closed
|
||||
closed = true;
|
||||
return len;
|
||||
}
|
||||
|
||||
uint32_t BaseContainer::skipBytes(uint32_t num) {
|
||||
return readBytes(nullptr, num);
|
||||
}
|
||||
|
||||
uint32_t BaseContainer::skipTo(uint32_t offset) {
|
||||
if (offset <= pos)
|
||||
return 0;
|
||||
return readBytes(nullptr, offset - pos);
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-8.
|
||||
|
||||
#include "Mpeg4Container.h"
|
||||
#include "AudioCodecs.h"
|
||||
#include "Mpeg4Atoms.h"
|
||||
#include "Mpeg4Types.h"
|
||||
|
||||
Mpeg4Container::~Mpeg4Container() {
|
||||
freeAll();
|
||||
this->source->close();
|
||||
}
|
||||
|
||||
void Mpeg4Container::feed(const std::shared_ptr<bell::ByteStream> &stream, uint32_t position) {
|
||||
BaseContainer::feed(stream, position);
|
||||
if (isParsed) {
|
||||
// this is needed to support seeking backwards, as goToData() always moves forward only
|
||||
setCurrentFragment();
|
||||
isInData = false;
|
||||
goToData();
|
||||
}
|
||||
}
|
||||
|
||||
bool Mpeg4Container::parse() {
|
||||
freeAll();
|
||||
if (pos != 0) {
|
||||
isParsed = false;
|
||||
return false;
|
||||
}
|
||||
uint32_t size;
|
||||
AtomType type;
|
||||
uint32_t moovEnd = 0, mdiaEnd = 0;
|
||||
bool parsed = false, error = false, hasMoov = false, hasHdlr = false;
|
||||
while (!parsed && !error && !closed) {
|
||||
readAtomHeader(size, (uint32_t &)type);
|
||||
switch (type) {
|
||||
/*case AtomType::ATOM_FTYP:
|
||||
readBytes(mediaBrand, 4);
|
||||
mediaBrand[4] = '\0';
|
||||
skipBytes(size - 4);
|
||||
break;*/
|
||||
|
||||
case AtomType::ATOM_MVEX:
|
||||
case AtomType::ATOM_TRAK:
|
||||
case AtomType::ATOM_MINF:
|
||||
case AtomType::ATOM_STBL:
|
||||
// this causes the next iteration to read the next direct child atom
|
||||
continue;
|
||||
case AtomType::ATOM_MOOV:
|
||||
moovEnd = pos + size;
|
||||
hasMoov = true;
|
||||
continue;
|
||||
case AtomType::ATOM_MDIA:
|
||||
mdiaEnd = pos + size;
|
||||
continue;
|
||||
|
||||
case AtomType::ATOM_TREX:
|
||||
readTrex();
|
||||
break;
|
||||
case AtomType::ATOM_TKHD:
|
||||
skipBytes(12);
|
||||
if (audioTrackId == -1) {
|
||||
audioTrackId = (int8_t)readUint32();
|
||||
skipBytes(size - 16);
|
||||
} else {
|
||||
// new track header, but audio track already found
|
||||
skipTo(moovEnd);
|
||||
}
|
||||
break;
|
||||
case AtomType::ATOM_MDHD:
|
||||
skipBytes(12);
|
||||
timescale = readUint32();
|
||||
totalDuration = readUint32();
|
||||
totalDurationPresent = true;
|
||||
durationMs = totalDuration * 1000LL / timescale;
|
||||
if (!sampleRate)
|
||||
sampleRate = timescale;
|
||||
hasHdlr = false;
|
||||
skipBytes(size - 20);
|
||||
break;
|
||||
case AtomType::ATOM_HDLR:
|
||||
if (hasHdlr) {
|
||||
skipBytes(size);
|
||||
continue;
|
||||
}
|
||||
hasHdlr = true;
|
||||
skipBytes(8);
|
||||
if (readUint32() != (uint32_t)AtomType::ATOM_SOUN) {
|
||||
skipTo(mdiaEnd); // skip the rest of mdia atom
|
||||
audioTrackId = -1; // unset the track ID, so the next tkhd can set it
|
||||
} else {
|
||||
skipBytes(size - 12);
|
||||
}
|
||||
break;
|
||||
case AtomType::ATOM_STSD:
|
||||
readStsd();
|
||||
break;
|
||||
case AtomType::ATOM_STTS:
|
||||
readStts();
|
||||
break;
|
||||
case AtomType::ATOM_STSC:
|
||||
readStsc();
|
||||
break;
|
||||
case AtomType::ATOM_STCO:
|
||||
readStco();
|
||||
break;
|
||||
case AtomType::ATOM_STSZ:
|
||||
readStsz();
|
||||
break;
|
||||
case AtomType::ATOM_SIDX:
|
||||
readSidx(size);
|
||||
break;
|
||||
case AtomType::ATOM_MOOF:
|
||||
case AtomType::ATOM_MDAT:
|
||||
// the track can be accessed randomly if all the tables are set before parsing the first fragment
|
||||
isSeekable = fragmentsLen || (chunksLen && chunkOffsetsLen && samplesLen && sampleSizesLen);
|
||||
if (type == AtomType::ATOM_MOOF) {
|
||||
// this will seek to the start of mdat header
|
||||
error = !parseMoof(size);
|
||||
} else {
|
||||
// pos already points to sample data
|
||||
isInData = true;
|
||||
}
|
||||
parsed = true;
|
||||
break;
|
||||
default:
|
||||
// ignore unknown atoms
|
||||
skipBytes(size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sampleDescLen) {
|
||||
codec = getCodec(sampleDesc);
|
||||
}
|
||||
if (!hasMoov || audioTrackId == -1 || codec == AudioCodec::UNKNOWN) {
|
||||
// this is not a progressive MP4, can't be played
|
||||
// or has no audio tracks
|
||||
// or has an unknown audio codec
|
||||
freeAll();
|
||||
isParsed = false;
|
||||
return false;
|
||||
}
|
||||
if (isInData) {
|
||||
// [pos] points to mdat, create a dummy fragment for it
|
||||
createFragment()->duration = totalDuration;
|
||||
}
|
||||
isParsed = !error && !closed;
|
||||
setCurrentFragment();
|
||||
setCurrentSample();
|
||||
return isParsed;
|
||||
}
|
||||
|
||||
bool Mpeg4Container::parseMoof(uint32_t moofSize) {
|
||||
freeFragment();
|
||||
uint32_t size;
|
||||
AtomType type;
|
||||
uint32_t moofOffset = pos - 8;
|
||||
uint32_t moofEnd = pos + moofSize;
|
||||
uint32_t trafEnd = 0;
|
||||
|
||||
bool hasFragment = false;
|
||||
Mpeg4Fragment *fragment;
|
||||
for (fragment = fragments; fragment < fragments + fragmentsLen; fragment++) {
|
||||
if (isInFragment(fragment, pos)) {
|
||||
hasFragment = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (pos < moofEnd) {
|
||||
readAtomHeader(size, (uint32_t &)type);
|
||||
switch (type) {
|
||||
case AtomType::ATOM_TRAF:
|
||||
trafEnd = pos + size;
|
||||
continue;
|
||||
case AtomType::ATOM_TFHD:
|
||||
readTfhd(trafEnd, moofOffset);
|
||||
break;
|
||||
case AtomType::ATOM_TRUN:
|
||||
readTrun(size, moofOffset);
|
||||
break;
|
||||
default:
|
||||
skipBytes(size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// this moof is not in the fragments table
|
||||
if (!hasFragment) {
|
||||
fragment = createFragment();
|
||||
}
|
||||
if (!totalDurationPresent) {
|
||||
// total duration was not found or a new fragment was created
|
||||
uint32_t duration = 0;
|
||||
for (Mpeg4SampleRange *sr = samples; sr < samples + samplesLen; sr++) {
|
||||
duration += sr->count * sr->duration;
|
||||
}
|
||||
fragment->duration = duration;
|
||||
totalDuration += duration;
|
||||
durationMs = totalDuration * 1000LL / timescale;
|
||||
}
|
||||
isFragmented = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t Mpeg4Container::getLoadingOffset(uint32_t timeMs) {
|
||||
if (!isParsed)
|
||||
return SAMPLE_NOT_LOADED;
|
||||
if (!isSeekable)
|
||||
return SAMPLE_NOT_SEEKABLE;
|
||||
// timeScaled - specified time in the media time coordinate system
|
||||
uint64_t timeScaled = (uint64_t)timeMs * timescale / 1000LL;
|
||||
uint64_t timeAbs = 0;
|
||||
|
||||
Mpeg4Fragment *fragment = fragments;
|
||||
for (; fragment < fragments + fragmentsLen; fragment++) {
|
||||
timeAbs += fragment->duration; // timeAbs holds the fragment end time
|
||||
if (timeScaled < timeAbs) {
|
||||
timeAbs -= fragment->duration; // set timeAbs to fragment start time
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!fragment)
|
||||
return SAMPLE_NOT_FOUND;
|
||||
if (fragment != curFragment)
|
||||
return (int32_t)fragment->start;
|
||||
// get the position in bytes
|
||||
return (int32_t)findSample((int64_t)timeScaled, -1, timeAbs);
|
||||
}
|
||||
|
||||
bool Mpeg4Container::goToData() {
|
||||
if (!isParsed || !curFragment)
|
||||
return false;
|
||||
if (isInData)
|
||||
return true;
|
||||
uint32_t size;
|
||||
AtomType type;
|
||||
|
||||
if (pos == curFragment->start || pos >= curFragment->end) {
|
||||
// fragment ended, or a new one just loaded
|
||||
while (pos >= curFragment->end && curFragment < fragments + fragmentsLen - 1) {
|
||||
// skip to the next fragment header
|
||||
curFragment++;
|
||||
} // else, no more **loaded** fragments
|
||||
if (pos < curFragment->start && !skipTo(curFragment->start))
|
||||
return false;
|
||||
// [pos] is either a fragment header, EOF or unknown data
|
||||
readAtomHeader(size, (uint32_t &)type);
|
||||
if (type == AtomType::ATOM_MOOF) {
|
||||
// fragment header found, try to parse it
|
||||
parseMoof(size);
|
||||
// update [curFragment]
|
||||
setCurrentFragment();
|
||||
// read mdat header
|
||||
readAtomHeader(size, (uint32_t &)type);
|
||||
}
|
||||
if (type != AtomType::ATOM_MDAT)
|
||||
return false;
|
||||
} else if (pos >= curChunk.end) {
|
||||
// chunk ended, but still in [curFragment]
|
||||
if (!curChunk.nextStart) // no more chunks but fragment not ended ??
|
||||
return false;
|
||||
if (pos != curChunk.nextStart && !skipTo(curChunk.nextStart))
|
||||
return false;
|
||||
} /* else {
|
||||
readAtomHeader(size, (uint32_t &)type);
|
||||
return false;
|
||||
}*/
|
||||
// update [isInData], [curChunk] and [curSampleSize]
|
||||
setCurrentSample();
|
||||
if (pos < curChunk.start) {
|
||||
// chunk not started yet, probably a multi-track movie
|
||||
if (!skipTo(curChunk.start))
|
||||
return false;
|
||||
// update [isInData], [curChunk] and [curSampleSize]
|
||||
setCurrentSample();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mpeg4Container::seekTo(uint32_t timeMs) {
|
||||
if (!isParsed || !isSeekable)
|
||||
return false;
|
||||
// try to go to nearest mdat data
|
||||
if (!goToData())
|
||||
return false;
|
||||
uint32_t offset = getLoadingOffset(timeMs);
|
||||
// check if the required [offset] is in the currently loaded fragment
|
||||
// - if it is, [offset] points to the required sample
|
||||
// - if it isn't, [offset] points to a moof header
|
||||
if (!isInFragment(curFragment, offset)) {
|
||||
// try to seek to moof header, fail if not possible
|
||||
if (offset != pos && !skipTo(offset))
|
||||
return false;
|
||||
// [pos] points to a moof header
|
||||
isInData = false;
|
||||
// parse the just loaded atom header (pos >= curFragment->end)
|
||||
if (!goToData())
|
||||
return false;
|
||||
// get the actual sample's offset
|
||||
offset = getLoadingOffset(timeMs);
|
||||
// ...or give up if still not loaded
|
||||
if (!isInFragment(curFragment, offset))
|
||||
return false;
|
||||
}
|
||||
if (!isInData) // something is really not ok
|
||||
return false;
|
||||
// [pos] points to mdat data
|
||||
// [offset] points to the required sample
|
||||
if (!skipTo(offset))
|
||||
return false;
|
||||
// update the current chunk range and sample sizes
|
||||
setCurrentSample();
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t Mpeg4Container::getCurrentTimeMs() {
|
||||
if (!curFragment || !isParsed)
|
||||
return 0;
|
||||
int64_t time = 0;
|
||||
// get time offset of the current fragment
|
||||
Mpeg4Fragment *f = fragments;
|
||||
while (f != curFragment) {
|
||||
time += (f++)->duration;
|
||||
}
|
||||
time = findSample(-1, (int32_t)pos, time);
|
||||
if (time < 0)
|
||||
return (int32_t)time;
|
||||
return (int32_t)(time * 1000LL / timescale);
|
||||
}
|
||||
|
||||
uint8_t *Mpeg4Container::readSample(uint32_t &len) {
|
||||
if (!curFragment || !isParsed)
|
||||
return nullptr;
|
||||
if (!sampleData) {
|
||||
allocSampleData();
|
||||
}
|
||||
// go to mdat
|
||||
if (!isInData && !goToData())
|
||||
return nullptr;
|
||||
len = *curSampleSize;
|
||||
len = readBytes(sampleData, len);
|
||||
skipBytes(*curSampleSize - len); // skip the rest if something went wrong
|
||||
if (sampleSizesLen > 1)
|
||||
curSampleSize++;
|
||||
if (pos >= curChunk.end) {
|
||||
// chunk ended, make goToData() read the next one
|
||||
isInData = false;
|
||||
}
|
||||
return sampleData;
|
||||
}
|
||||
|
||||
uint8_t *Mpeg4Container::getSetupData(uint32_t &len, AudioCodec matchCodec) {
|
||||
for (SampleDescription *desc = sampleDesc; desc < sampleDesc + sampleDescLen; desc++) {
|
||||
if (matchCodec != getCodec(desc))
|
||||
continue;
|
||||
len = desc->dataLength;
|
||||
return desc->data;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-10.
|
||||
|
||||
#include "BellUtils.h"
|
||||
#include "Mpeg4Container.h"
|
||||
#include "Mpeg4Types.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
/** Populate [chunks] using the Sample-to-chunk Table */
|
||||
void Mpeg4Container::readStsc() {
|
||||
skipBytes(4); // skip version and flags
|
||||
chunksLen = readUint32();
|
||||
chunks = (Mpeg4ChunkRange *)malloc(chunksLen * sizeof(Mpeg4ChunkRange));
|
||||
for (uint32_t i = 0; i < chunksLen; i++) {
|
||||
chunks[i].count = readUint32();
|
||||
chunks[i].samples = readUint32();
|
||||
chunks[i].sampleDescriptionId = readUint32();
|
||||
if (i > 0) {
|
||||
chunks[i - 1].count = chunks[i].count - chunks[i - 1].count;
|
||||
}
|
||||
}
|
||||
if (chunkOffsetsLen) {
|
||||
chunks[chunksLen - 1].count = chunkOffsetsLen - chunks[chunksLen - 1].count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Populate [chunkOffsets] using the Chunk Offset Table */
|
||||
void Mpeg4Container::readStco() {
|
||||
skipBytes(4); // skip version and flags
|
||||
chunkOffsetsLen = readUint32();
|
||||
chunkOffsets = (Mpeg4ChunkOffset *)malloc(chunkOffsetsLen * sizeof(Mpeg4ChunkOffset));
|
||||
for (uint32_t i = 0; i < chunkOffsetsLen; i++) {
|
||||
chunkOffsets[i] = readUint32();
|
||||
}
|
||||
if (chunksLen) {
|
||||
chunks[chunksLen - 1].count = chunkOffsetsLen - chunks[chunksLen - 1].count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Populate [samples] using the Time-to-sample Table */
|
||||
void Mpeg4Container::readStts() {
|
||||
skipBytes(4); // skip version and flags
|
||||
samplesLen = readUint32();
|
||||
samples = (Mpeg4SampleRange *)malloc(samplesLen * sizeof(Mpeg4SampleRange));
|
||||
for (uint32_t i = 0; i < samplesLen; i++) {
|
||||
samples[i].count = readUint32();
|
||||
samples[i].duration = readUint32();
|
||||
}
|
||||
}
|
||||
|
||||
/** Populate [sampleSizes] using the Sample Size Table */
|
||||
void Mpeg4Container::readStsz() {
|
||||
skipBytes(4); // skip version and flags
|
||||
uint32_t sampleSize = readUint32();
|
||||
sampleSizesLen = readUint32();
|
||||
if (sampleSize) {
|
||||
sampleSizesLen = 1;
|
||||
}
|
||||
sampleSizes = (Mpeg4SampleSize *)malloc(sampleSizesLen * sizeof(Mpeg4SampleSize));
|
||||
if (sampleSize) {
|
||||
sampleSizes[0] = sampleSize;
|
||||
if (sampleSize > sampleSizeMax)
|
||||
sampleSizeMax = sampleSize;
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = 0; i < sampleSizesLen; i++) {
|
||||
sampleSize = readUint32();
|
||||
if (sampleSize > sampleSizeMax)
|
||||
sampleSizeMax = sampleSize;
|
||||
sampleSizes[i] = sampleSize;
|
||||
}
|
||||
// reallocate sampleData if the max size changes
|
||||
allocSampleData();
|
||||
}
|
||||
|
||||
/** Populate [sampleDesc] using the Sample Description Table */
|
||||
void Mpeg4Container::readStsd() {
|
||||
// Helpful resources:
|
||||
// - STSD atom structure - ISO/IEC 14496-1 (page 277) - seems to cover QT desc ver.0
|
||||
// - ESDS atom structure - ISO/IEC 14496-1 (page 28)
|
||||
freeAndNull((void *&)sampleDesc);
|
||||
skipBytes(4); // skip version and flags
|
||||
sampleDescLen = readUint32();
|
||||
sampleDesc = (SampleDescription *)malloc(sampleDescLen * sizeof(SampleDescription));
|
||||
for (SampleDescription *desc = sampleDesc; desc < sampleDesc + sampleDescLen; desc++) {
|
||||
uint32_t entryEnd = readUint32() - 4 + pos;
|
||||
uint32_t esdsEnd = entryEnd;
|
||||
// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCHHGBH
|
||||
// General Structure of a Sample Description
|
||||
desc->format = (AudioSampleFormat)readUint32();
|
||||
desc->mp4aObjectType = MP4AObjectType::UNDEFINED;
|
||||
desc->mp4aProfile = MP4AProfile::UNDEFINED;
|
||||
skipBytes(6); // reserved
|
||||
desc->dataReferenceIndex = readUint16();
|
||||
// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-75770
|
||||
// Sound Sample Description (Version 0)
|
||||
uint16_t version = readUint16();
|
||||
skipBytes(6); // skip Revision level(2), Vendor(4)
|
||||
channelCount = readUint16();
|
||||
bitDepth = readUint16();
|
||||
skipBytes(4); // skip Compression ID(2), Packet size(2)
|
||||
sampleRate = readUint16();
|
||||
skipBytes(2); // decimal part of sample rate
|
||||
if (version >= 1) {
|
||||
// Sound Sample Description (Version 1)
|
||||
skipBytes(16); // skip Samples per packet(4), Bytes per packet(4), Bytes per frame(4), Bytes per sample(4)
|
||||
}
|
||||
// read the child atom
|
||||
uint32_t atomSize;
|
||||
AtomType atomType;
|
||||
readAtomHeader(atomSize, (uint32_t &)atomType);
|
||||
if (atomType == AtomType::ATOM_WAVE) {
|
||||
do {
|
||||
readAtomHeader(atomSize, (uint32_t &)atomType);
|
||||
if (atomType == AtomType::ATOM_ESDS) {
|
||||
esdsEnd = pos + atomSize;
|
||||
break;
|
||||
}
|
||||
skipBytes(atomSize);
|
||||
} while (pos < entryEnd);
|
||||
if (pos >= entryEnd) // something went wrong
|
||||
continue;
|
||||
}
|
||||
if (atomType != AtomType::ATOM_ESDS) {
|
||||
desc->dataType = (uint32_t)atomType;
|
||||
desc->dataLength = atomSize;
|
||||
desc->data = (uint8_t *)malloc(desc->dataLength);
|
||||
readBytes(desc->data, desc->dataLength);
|
||||
continue;
|
||||
}
|
||||
// read ESDS
|
||||
skipBytes(4); // skip esds flags
|
||||
while (pos < esdsEnd) {
|
||||
uint8_t tag = readUint8();
|
||||
uint32_t size = readVarint32();
|
||||
uint8_t flags;
|
||||
switch (tag) {
|
||||
case 0x03: // ES_Descriptor
|
||||
skipBytes(2);
|
||||
flags = readUint8();
|
||||
if (flags & 0b10000000)
|
||||
skipBytes(2);
|
||||
if (flags & 0b01000000)
|
||||
skipBytes(readUint8());
|
||||
if (flags & 0b00100000)
|
||||
skipBytes(2);
|
||||
break;
|
||||
case 0x04: // DecoderConfigDescriptor
|
||||
desc->mp4aObjectType = (MP4AObjectType)readUint8();
|
||||
skipBytes(12);
|
||||
break;
|
||||
case 0x05: // DecoderSpecificInfo
|
||||
if (desc->mp4aObjectType == MP4AObjectType::MP4A) {
|
||||
desc->mp4aProfile = (MP4AProfile)(readUint8() >> 3);
|
||||
skipBytes(size - 1);
|
||||
} else {
|
||||
desc->dataType = 0;
|
||||
desc->dataLength = size;
|
||||
desc->data = (uint8_t *)malloc(desc->dataLength);
|
||||
readBytes(desc->data, desc->dataLength);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
skipBytes(size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// skip leftover atoms for version 1 QuickTime descriptors
|
||||
skipTo(entryEnd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-10.
|
||||
|
||||
#include "Mpeg4Container.h"
|
||||
|
||||
/** Populate [fragments] using the Segment Index Table */
|
||||
void Mpeg4Container::readSidx(uint32_t atomSize) {
|
||||
// https://b.goeswhere.com/ISO_IEC_14496-12_2015.pdf (page 121)
|
||||
uint32_t offset = pos + atomSize;
|
||||
uint8_t version = readUint8();
|
||||
skipBytes(3); // skip flags
|
||||
if (audioTrackId != readUint32()) { // check reference_ID
|
||||
skipBytes(atomSize - 8); // skip the rest
|
||||
return;
|
||||
}
|
||||
skipBytes(4); // skip uint(32) timescale
|
||||
skipBytes(version ? 12 : 4); // skip zeroes, depending on version
|
||||
offset += readUint32(); // read first_offset
|
||||
skipBytes(2); // skip uint(16) reserved
|
||||
|
||||
fragmentsLen = readUint16();
|
||||
fragments = (Mpeg4Fragment *)malloc(fragmentsLen * sizeof(Mpeg4Fragment));
|
||||
for (uint32_t i = 0; i < fragmentsLen; i++) {
|
||||
auto size = readUint32();
|
||||
if (size & (1 << 31)) {
|
||||
skipBytes(8);
|
||||
continue; // ignore references to sidx, for now
|
||||
}
|
||||
fragments[i].start = offset;
|
||||
fragments[i].end = offset + size;
|
||||
fragments[i].duration = readUint32();
|
||||
offset += size;
|
||||
skipBytes(4); // skip SAP info
|
||||
}
|
||||
isFragmented = true;
|
||||
}
|
||||
|
||||
/** Populate [sampleDefs] using Track Extends */
|
||||
void Mpeg4Container::readTrex() {
|
||||
// https://b.goeswhere.com/ISO_IEC_14496-12_2015.pdf (page 69)
|
||||
skipBytes(4); // skip version and flags
|
||||
sampleDefsLen++;
|
||||
sampleDefs = (SampleDefaults *)realloc(sampleDefs, sampleDefsLen * sizeof(SampleDefaults));
|
||||
sampleDefTracks = (uint32_t *)realloc(sampleDefTracks, sampleDefsLen * sizeof(uint32_t));
|
||||
uint32_t i = sampleDefsLen - 1;
|
||||
sampleDefTracks[i] = readUint32();
|
||||
sampleDefs[i].offset = 0;
|
||||
sampleDefs[i].sampleDescriptionId = readUint32();
|
||||
sampleDefs[i].duration = readUint32();
|
||||
sampleDefs[i].size = readUint32();
|
||||
sampleDefs[i].flags = readUint32();
|
||||
}
|
||||
|
||||
/** Populate [sampleDefs] using Track Fragment Header */
|
||||
void Mpeg4Container::readTfhd(uint32_t trafEnd, uint32_t moofOffset) {
|
||||
skipBytes(1); // skip version
|
||||
TfFlags tfhdFlags = {};
|
||||
readBytes((uint8_t *)&tfhdFlags, 3);
|
||||
if (audioTrackId != readUint32()) {
|
||||
skipTo(trafEnd); // skip the rest of traf
|
||||
return;
|
||||
}
|
||||
auto *def = getSampleDef(audioTrackId);
|
||||
if (!def) {
|
||||
skipTo(trafEnd); // error?
|
||||
return;
|
||||
}
|
||||
def->offset = 0;
|
||||
if (tfhdFlags.baseDataOffsetPresent)
|
||||
def->offset = readUint64();
|
||||
if (tfhdFlags.sampleDescriptionIndexPresent)
|
||||
def->sampleDescriptionId = readUint32();
|
||||
if (tfhdFlags.defaultSampleDurationPresent)
|
||||
def->duration = readUint32();
|
||||
if (tfhdFlags.defaultSampleSizePresent)
|
||||
def->size = readUint32();
|
||||
if (tfhdFlags.defaultSampleFlagsPresent)
|
||||
def->flags = readUint32();
|
||||
if (tfhdFlags.defaultBaseIsMoof)
|
||||
def->offset += moofOffset;
|
||||
}
|
||||
|
||||
/** Populate [chunks, chunkOffsets, samples, sampleSizes] using Track Fragment Run Table */
|
||||
void Mpeg4Container::readTrun(uint32_t atomSize, uint32_t moofOffset) {
|
||||
skipBytes(1); // skip version
|
||||
TrFlags trunFlags = {};
|
||||
readBytes((uint8_t *)&trunFlags, 3);
|
||||
// audioTrackId is guaranteed to match this trun's track ID
|
||||
auto *def = getSampleDef(audioTrackId);
|
||||
if (!def) {
|
||||
skipBytes(atomSize - 4); // error?
|
||||
return;
|
||||
}
|
||||
uint32_t i, j = 0;
|
||||
uint32_t sampleCnt = readUint32();
|
||||
uint32_t offset = def->offset ? def->offset : moofOffset; // base offset is baseDataOffset or moofOffset
|
||||
// SampleFlags flags = def->flags;
|
||||
if (trunFlags.dataOffsetPresent)
|
||||
offset += readUint32();
|
||||
// if (trunFlags.firstSampleFlagsPresent)
|
||||
// flags = readUint32();
|
||||
|
||||
// treat every trun as a single new chunk
|
||||
i = chunksLen++;
|
||||
chunks = (Mpeg4ChunkRange *)realloc(chunks, chunksLen * sizeof(Mpeg4ChunkRange));
|
||||
chunks[i].count = 1;
|
||||
chunks[i].samples = sampleCnt;
|
||||
chunks[i].sampleDescriptionId = def->sampleDescriptionId;
|
||||
i = chunkOffsetsLen++;
|
||||
chunkOffsets = (Mpeg4ChunkOffset *)realloc(chunkOffsets, chunkOffsetsLen * sizeof(Mpeg4ChunkOffset));
|
||||
chunkOffsets[i] = offset;
|
||||
|
||||
// add all samples' sizes from this trun
|
||||
i = sampleSizesLen;
|
||||
sampleSizesLen += sampleCnt;
|
||||
sampleSizes = (Mpeg4SampleSize *)realloc(sampleSizes, sampleSizesLen * sizeof(Mpeg4SampleSize));
|
||||
// count duration changes for Mpeg4SampleRanges
|
||||
auto *durations = (uint32_t *)malloc(sampleCnt * sizeof(uint32_t));
|
||||
uint32_t prevDuration = 0, durationChanges = 0;
|
||||
|
||||
// TODO optimize memory usage for when all samples are of equal sizes
|
||||
for (; i < sampleSizesLen; i++) {
|
||||
durations[j] = trunFlags.sampleDurationPresent ? readUint32() : def->duration;
|
||||
sampleSizes[i] = trunFlags.sampleSizePresent ? readUint32() : def->size;
|
||||
if (sampleSizes[i] > sampleSizeMax)
|
||||
sampleSizeMax = sampleSizes[i];
|
||||
if (trunFlags.sampleFlagsPresent)
|
||||
skipBytes(4); // skip flags, for now
|
||||
if (trunFlags.sampleCompositionTimeOffsetsPresent)
|
||||
skipBytes(4); // skip sample_composition_time_offset
|
||||
// count duration changes
|
||||
if (durations[j] != prevDuration) {
|
||||
prevDuration = durations[j];
|
||||
durationChanges++;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
// add each duration change as a sample range
|
||||
i = samplesLen;
|
||||
samplesLen += durationChanges;
|
||||
samples = (Mpeg4SampleRange *)realloc(samples, samplesLen * sizeof(Mpeg4SampleRange));
|
||||
prevDuration = 0;
|
||||
uint32_t durationCnt = 0; // how many consecutive samples have this duration
|
||||
for (j = 0; j < sampleCnt; j++) {
|
||||
if (durations[j] != prevDuration) {
|
||||
if (prevDuration) {
|
||||
samples[i].count = durationCnt;
|
||||
samples[i].duration = prevDuration;
|
||||
}
|
||||
prevDuration = durations[j];
|
||||
durationCnt = 1;
|
||||
} else {
|
||||
durationCnt++;
|
||||
}
|
||||
}
|
||||
samples[samplesLen - 1].count = durationCnt;
|
||||
samples[samplesLen - 1].duration = prevDuration;
|
||||
// free temp array
|
||||
free(durations);
|
||||
// reallocate sampleData if the max size changes
|
||||
allocSampleData();
|
||||
}
|
||||
215
components/spotify/cspot/bell/src/audio/container/Mpeg4Utils.cpp
Normal file
215
components/spotify/cspot/bell/src/audio/container/Mpeg4Utils.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-10.
|
||||
|
||||
#include "AudioCodecs.h"
|
||||
#include "BellUtils.h"
|
||||
#include "Mpeg4Container.h"
|
||||
#include "Mpeg4Types.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
void Mpeg4Container::readAtomHeader(uint32_t &size, uint32_t &type) {
|
||||
size = readUint32() - 8;
|
||||
type = readUint32();
|
||||
}
|
||||
|
||||
void Mpeg4Container::allocSampleData() {
|
||||
if (sampleSizeMax != sampleDataLen) {
|
||||
freeAndNull((void *&)sampleData);
|
||||
sampleData = (uint8_t *)realloc(sampleData, sampleSizeMax);
|
||||
sampleDataLen = sampleSizeMax;
|
||||
}
|
||||
}
|
||||
|
||||
void Mpeg4Container::freeAll() {
|
||||
freeAndNull((void *&)fragments);
|
||||
fragmentsLen = 0;
|
||||
freeAndNull((void *&)sampleDefs);
|
||||
freeAndNull((void *&)sampleDefTracks);
|
||||
sampleDefsLen = 0;
|
||||
for (SampleDescription *desc = sampleDesc; desc < sampleDesc + sampleDefsLen; desc++) {
|
||||
free(desc->data);
|
||||
}
|
||||
freeAndNull((void *&)sampleDesc);
|
||||
sampleDescLen = 0;
|
||||
freeAndNull((void *&)sampleData);
|
||||
freeFragment();
|
||||
}
|
||||
|
||||
void Mpeg4Container::freeFragment() {
|
||||
freeAndNull((void *&)chunks);
|
||||
chunksLen = 0;
|
||||
freeAndNull((void *&)chunkOffsets);
|
||||
chunkOffsetsLen = 0;
|
||||
freeAndNull((void *&)samples);
|
||||
samplesLen = 0;
|
||||
freeAndNull((void *&)sampleSizes);
|
||||
sampleSizesLen = 0;
|
||||
}
|
||||
|
||||
SampleDefaults *Mpeg4Container::getSampleDef(uint32_t trackId) {
|
||||
for (uint32_t i = 0; i < sampleDefsLen; i++) {
|
||||
if (sampleDefTracks[i] == trackId)
|
||||
return sampleDefs + i;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Mpeg4Container::isInFragment(Mpeg4Fragment *f, uint32_t offset) {
|
||||
return offset >= f->start && offset < f->end;
|
||||
}
|
||||
|
||||
void Mpeg4Container::setCurrentFragment() {
|
||||
if (!isParsed)
|
||||
return;
|
||||
for (Mpeg4Fragment *f = fragments; f < fragments + fragmentsLen; f++) {
|
||||
if (isInFragment(f, pos)) {
|
||||
curFragment = f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
curFragment = nullptr;
|
||||
}
|
||||
|
||||
void Mpeg4Container::setCurrentSample() {
|
||||
if (!isParsed)
|
||||
return;
|
||||
Mpeg4ChunkRange *chunk = chunks;
|
||||
Mpeg4ChunkOffset *chunkOffset = chunkOffsets;
|
||||
uint32_t chunkCnt = chunk->count;
|
||||
uint32_t chunkSampleCnt = chunk->samples;
|
||||
curChunk.start = 0;
|
||||
curChunk.end = 0;
|
||||
uint32_t offset = *chunkOffset;
|
||||
Mpeg4SampleSize *ss = sampleSizes;
|
||||
while (ss < sampleSizes + sampleSizesLen) {
|
||||
// for (Mpeg4SampleSize *ss = sampleSizes; ss < sampleSizes + sampleSizesLen; ss++) {
|
||||
offset += *ss;
|
||||
if (!curChunk.start && pos < offset) { // sample found
|
||||
curChunk.start = chunkOffset ? *chunkOffset : 0; // set chunk beginning
|
||||
curSampleSize = ss; // store reference to current sample
|
||||
}
|
||||
chunkSampleCnt--; // decrease remaining samples in chunk
|
||||
if (!chunkSampleCnt) { // no more samples
|
||||
chunkOffset++; // get next chunk offset
|
||||
if (chunkOffset >= chunkOffsets + chunkOffsetsLen)
|
||||
chunkOffset = nullptr;
|
||||
if (curChunk.start) { // chunk ended and beginning already found
|
||||
curChunk.end = offset; // set chunk end
|
||||
curChunk.nextStart = chunkOffset ? *chunkOffset : 0; // set next chunk offset
|
||||
break;
|
||||
}
|
||||
if (chunkOffset)
|
||||
offset = *chunkOffset;
|
||||
chunkCnt--; // decrease remaining chunks in range
|
||||
if (!chunkCnt) { // no more chunks
|
||||
chunk++; // get next chunk range
|
||||
if (chunk >= chunks + chunksLen) // something is not ok
|
||||
return; // -> fail
|
||||
chunkCnt = chunk->count; // update new chunk count from range
|
||||
}
|
||||
chunkSampleCnt = chunk->samples; // update new sample count from range
|
||||
}
|
||||
if (sampleSizesLen > 1)
|
||||
ss++;
|
||||
}
|
||||
isInData = pos >= curChunk.start && pos < curChunk.end;
|
||||
}
|
||||
|
||||
Mpeg4Fragment *Mpeg4Container::createFragment() {
|
||||
uint32_t i = fragmentsLen++;
|
||||
fragments = (Mpeg4Fragment *)realloc(fragments, fragmentsLen * sizeof(Mpeg4Fragment));
|
||||
fragments[i].start = pos - 8;
|
||||
uint32_t fragmentEnd = chunkOffsets[chunkOffsetsLen - 1];
|
||||
uint32_t lastRangeSamples = chunks[chunksLen - 1].samples;
|
||||
if (sampleSizesLen == 1)
|
||||
fragmentEnd += *sampleSizes * lastRangeSamples;
|
||||
else {
|
||||
for (uint32_t j = sampleSizesLen - lastRangeSamples; j < sampleSizesLen; j++) {
|
||||
fragmentEnd += sampleSizes[j];
|
||||
}
|
||||
}
|
||||
fragments[i].end = fragmentEnd;
|
||||
fragments[i].duration = 0;
|
||||
totalDurationPresent = false;
|
||||
return fragments + i;
|
||||
}
|
||||
|
||||
AudioCodec Mpeg4Container::getCodec(SampleDescription *desc) {
|
||||
switch (desc->format) {
|
||||
case AudioSampleFormat::OPUS:
|
||||
return AudioCodec::OPUS;
|
||||
case AudioSampleFormat::FLAC:
|
||||
return AudioCodec::FLAC;
|
||||
case AudioSampleFormat::MP4A:
|
||||
switch (desc->mp4aObjectType) {
|
||||
case MP4AObjectType::AAC_LC:
|
||||
return AudioCodec::AAC;
|
||||
case MP4AObjectType::OPUS:
|
||||
return AudioCodec::OPUS;
|
||||
case MP4AObjectType::VORBIS:
|
||||
return AudioCodec::VORBIS;
|
||||
case MP4AObjectType::MPEG1:
|
||||
return AudioCodec::MP3;
|
||||
case MP4AObjectType::MP4A:
|
||||
switch (desc->mp4aProfile) {
|
||||
case MP4AProfile::AAC_LC:
|
||||
return AudioCodec::AAC;
|
||||
case MP4AProfile::LAYER_3:
|
||||
return AudioCodec::MP3;
|
||||
default:
|
||||
return AudioCodec::UNKNOWN;
|
||||
}
|
||||
default:
|
||||
return AudioCodec::UNKNOWN;
|
||||
}
|
||||
default:
|
||||
return AudioCodec::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t Mpeg4Container::findSample(int64_t byTime, int32_t byPos, uint64_t startTime) {
|
||||
Mpeg4ChunkRange *chunk = chunks;
|
||||
Mpeg4SampleRange *sample = samples;
|
||||
Mpeg4ChunkOffset *chunkOffset = chunkOffsets;
|
||||
Mpeg4SampleSize *sampleSize = sampleSizes;
|
||||
uint32_t chunkCnt = chunk->count;
|
||||
uint32_t chunkSampleCnt = chunk->samples;
|
||||
uint32_t sampleRangeCnt = sample->count;
|
||||
|
||||
uint64_t timeAbs = startTime;
|
||||
uint32_t offsetAbs = *chunkOffset;
|
||||
while (sampleSize < sampleSizes + sampleSizesLen) {
|
||||
if (byTime >= 0 && byTime <= timeAbs) {
|
||||
return offsetAbs;
|
||||
}
|
||||
if (byPos >= 0 && byPos <= offsetAbs) {
|
||||
return (int64_t)timeAbs;
|
||||
}
|
||||
timeAbs += sample->duration;
|
||||
sampleRangeCnt--;
|
||||
if (!sampleRangeCnt) {
|
||||
sample++;
|
||||
if (sample > samples + samplesLen)
|
||||
return SAMPLE_NOT_FOUND;
|
||||
sampleRangeCnt = sample->count;
|
||||
}
|
||||
chunkSampleCnt--;
|
||||
if (!chunkSampleCnt) {
|
||||
chunkCnt--;
|
||||
chunkOffset++;
|
||||
if (chunkOffset > chunkOffsets + chunkOffsetsLen)
|
||||
return SAMPLE_NOT_FOUND;
|
||||
offsetAbs = *chunkOffset;
|
||||
if (!chunkCnt) {
|
||||
chunk++;
|
||||
if (chunk > chunks + chunksLen)
|
||||
return SAMPLE_NOT_FOUND;
|
||||
chunkCnt = chunk->count;
|
||||
}
|
||||
chunkSampleCnt = chunk->samples;
|
||||
} else {
|
||||
offsetAbs += sampleSizesLen > 1 ? *(sampleSize++) : *sampleSize;
|
||||
}
|
||||
}
|
||||
return SAMPLE_NOT_FOUND;
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-16.
|
||||
|
||||
#include "WebmContainer.h"
|
||||
#include "AudioCodecs.h"
|
||||
#include "BellUtils.h"
|
||||
#include "WebmElements.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
#define BLOCK_LACING_MASK 0b110
|
||||
|
||||
WebmContainer::~WebmContainer() {
|
||||
freeAndNull((void *&)docType);
|
||||
freeAndNull((void *&)codecId);
|
||||
freeAndNull((void *&)codecPrivate);
|
||||
freeAndNull((void *&)cues);
|
||||
freeAndNull((void *&)sampleData);
|
||||
freeAndNull((void *&)laceSizes);
|
||||
}
|
||||
|
||||
void WebmContainer::feed(const std::shared_ptr<bell::ByteStream> &stream, uint32_t position) {
|
||||
BaseContainer::feed(stream, position);
|
||||
freeAndNull((void *&)laceSizes);
|
||||
laceLeft = 0;
|
||||
readOutFrameSize = 0;
|
||||
}
|
||||
|
||||
bool WebmContainer::parse() {
|
||||
bool segmentFound = false;
|
||||
do {
|
||||
readElem();
|
||||
switch (eid) {
|
||||
case ElementId::EBML:
|
||||
continue;
|
||||
case ElementId::DocType:
|
||||
docType = static_cast<char *>(malloc(esize + 1));
|
||||
docType[esize] = '\0';
|
||||
readBytes((uint8_t *)docType, esize);
|
||||
break;
|
||||
case ElementId::Segment:
|
||||
segmentFound = true;
|
||||
parseSegment(pos); // webm can only have a single segment
|
||||
break;
|
||||
default:
|
||||
skipBytes(esize);
|
||||
}
|
||||
} while (!segmentFound);
|
||||
if (strncmp(codecId, "A_OPUS", 6) == 0)
|
||||
codec = AudioCodec::OPUS;
|
||||
else if (strncmp(codecId, "A_VORBIS", 8) == 0)
|
||||
codec = AudioCodec::VORBIS;
|
||||
else
|
||||
codec = AudioCodec::UNKNOWN;
|
||||
isSeekable = cuesLen;
|
||||
return isParsed && codec != AudioCodec::UNKNOWN;
|
||||
}
|
||||
|
||||
int32_t WebmContainer::getLoadingOffset(uint32_t timeMs) {
|
||||
if (!isSeekable || !cuesLen)
|
||||
return SAMPLE_NOT_SEEKABLE;
|
||||
auto offset = (int32_t)cues[0].offset;
|
||||
auto reqTime = (uint32_t)((float)timeMs * 1000000.0f / timescale);
|
||||
for (CuePoint *cue = cues + 1; cue < cues + cuesLen; cue++) {
|
||||
if (reqTime <= cue->time)
|
||||
return offset;
|
||||
offset = (int32_t)cue->offset;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool WebmContainer::seekTo(uint32_t timeMs) {
|
||||
auto reqTime = (uint32_t)((float)timeMs * 1000000.0f / timescale);
|
||||
if (reqTime <= currentTime)
|
||||
return false;
|
||||
// seeking ¯\_(ツ)_/¯
|
||||
readOutFrameSize = readCluster(reqTime);
|
||||
return !closed;
|
||||
}
|
||||
|
||||
int32_t WebmContainer::getCurrentTimeMs() {
|
||||
return (int32_t)((float)currentTime * timescale / 1000000.0f);
|
||||
}
|
||||
|
||||
uint8_t *WebmContainer::readSample(uint32_t &len) {
|
||||
if (readOutFrameSize)
|
||||
return readFrame(readOutFrameSize, len);
|
||||
if (laceLeft && laceLeft--)
|
||||
return readFrame(*(laceCurrent++), len);
|
||||
return readFrame(readCluster(), len);
|
||||
}
|
||||
|
||||
uint32_t WebmContainer::readCluster(uint32_t untilTime) {
|
||||
uint32_t end;
|
||||
do {
|
||||
readElem();
|
||||
end = pos + esize;
|
||||
switch (eid) {
|
||||
case ElementId::Cluster:
|
||||
continue;
|
||||
case ElementId::Timestamp:
|
||||
clusterTime = readUint(esize);
|
||||
break;
|
||||
case ElementId::BlockGroup:
|
||||
continue;
|
||||
case ElementId::Block:
|
||||
case ElementId::SimpleBlock:
|
||||
if (readVarNum32() != audioTrackId) {
|
||||
skipTo(end);
|
||||
continue;
|
||||
}
|
||||
currentTime = clusterTime + readUint16();
|
||||
if (!untilTime || currentTime >= untilTime)
|
||||
return readBlock(end);
|
||||
skipTo(end); // skip all unneeded frames
|
||||
break;
|
||||
default:
|
||||
skipBytes(esize);
|
||||
}
|
||||
} while (!closed);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t WebmContainer::readBlock(uint32_t end) {
|
||||
uint8_t lacing = readUint8() & BLOCK_LACING_MASK;
|
||||
// https://www.matroska.org/technical/basics.html#simpleblock-structure
|
||||
if (!lacing) // no lacing (0b000)
|
||||
return end - pos;
|
||||
// use lacing
|
||||
laceLeft = readUint8() + 1;
|
||||
freeAndNull((void *&)laceSizes);
|
||||
laceSizes = static_cast<uint32_t *>(malloc(laceLeft * sizeof(uint32_t)));
|
||||
auto *size = laceSizes;
|
||||
|
||||
for (uint8_t i = 0; i < laceLeft; i++) {
|
||||
if (lacing == 0b010) { // Xiph lacing (0b010)
|
||||
uint8_t sizeByte = readUint8();
|
||||
*size = sizeByte;
|
||||
while (sizeByte == 255) {
|
||||
sizeByte = readUint8();
|
||||
*size += sizeByte;
|
||||
}
|
||||
} else if (lacing == 0b110) { // EBML lacing (0b110)
|
||||
*size = readVarNum32();
|
||||
} else { // fixed-size lacing (0b100)
|
||||
*size = (end - pos) / laceLeft;
|
||||
}
|
||||
size++;
|
||||
}
|
||||
laceCurrent = laceSizes + 1;
|
||||
laceLeft--;
|
||||
return laceSizes[0];
|
||||
}
|
||||
|
||||
uint8_t *WebmContainer::readFrame(uint32_t size, uint32_t &outLen) {
|
||||
if (!size)
|
||||
return nullptr;
|
||||
if (size > sampleLen) {
|
||||
free(sampleData);
|
||||
sampleData = static_cast<uint8_t *>(malloc(size));
|
||||
sampleLen = size;
|
||||
}
|
||||
outLen = readBytes(sampleData, size);
|
||||
readOutFrameSize = 0;
|
||||
return sampleData;
|
||||
}
|
||||
|
||||
uint8_t *WebmContainer::getSetupData(uint32_t &len, AudioCodec matchCodec) {
|
||||
if (codec != matchCodec)
|
||||
return nullptr;
|
||||
len = codecPrivateLen;
|
||||
return codecPrivate;
|
||||
}
|
||||
126
components/spotify/cspot/bell/src/audio/container/WebmParser.cpp
Normal file
126
components/spotify/cspot/bell/src/audio/container/WebmParser.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-16.
|
||||
|
||||
#include "WebmContainer.h"
|
||||
#include "WebmElements.h"
|
||||
|
||||
void WebmContainer::parseSegment(uint32_t start) {
|
||||
uint16_t cueIdx = 0;
|
||||
do {
|
||||
readElem();
|
||||
switch (eid) {
|
||||
case ElementId::Info:
|
||||
case ElementId::Tracks:
|
||||
continue;
|
||||
case ElementId::TimestampScale:
|
||||
timescale = (float)readUint(esize);
|
||||
break;
|
||||
case ElementId::Duration:
|
||||
durationMs = (uint32_t)(readFloat(esize) * timescale / 1000000.0f);
|
||||
break;
|
||||
case ElementId::TrackEntry:
|
||||
if (audioTrackId == 255)
|
||||
parseTrack(pos + esize);
|
||||
else // skip other tracks if audio is already found
|
||||
skipBytes(esize);
|
||||
break;
|
||||
case ElementId::Cues:
|
||||
// try to guess the amount of CuePoints from the total size:
|
||||
// - CuePoint = id(1) + size(1) + CueTime + CueTrackPositions
|
||||
// - CueTime = id(1) + size(1) + value(2) # avg. 16 bit
|
||||
// - CueTrackPositions = id(1) + size(1) + CueTrack + CueClusterPosition
|
||||
// - CueTrack = id(1) + size(1) + value(1)
|
||||
// - CueClusterPosition = id(1) + size(1) + value(3) # avg. 24 bit
|
||||
// total: approx. 16 bytes
|
||||
cuesLen += esize / 16;
|
||||
cues = static_cast<CuePoint *>(realloc(cues, cuesLen * sizeof(CuePoint)));
|
||||
continue; // read the next child
|
||||
case ElementId::CuePoint:
|
||||
if (cueIdx >= cuesLen) {
|
||||
cuesLen++;
|
||||
cues = static_cast<CuePoint *>(realloc(cues, cuesLen * sizeof(CuePoint)));
|
||||
}
|
||||
parseCuePoint(cueIdx++, pos + esize, start);
|
||||
break;
|
||||
case ElementId::Cluster:
|
||||
isParsed = audioTrackId != 255;
|
||||
clusterEnd = pos + esize;
|
||||
return;
|
||||
default:
|
||||
skipBytes(esize);
|
||||
}
|
||||
} while (!isParsed);
|
||||
}
|
||||
|
||||
void WebmContainer::parseTrack(uint32_t end) {
|
||||
uint8_t trackId = 255;
|
||||
uint32_t trackRate = 0;
|
||||
uint8_t trackChannels = 0;
|
||||
uint8_t trackBits = 0;
|
||||
char *trackCodecId = nullptr;
|
||||
uint8_t *trackCodecPrivate = nullptr;
|
||||
uint32_t trackCodecPrivateLen = 0;
|
||||
do {
|
||||
readElem();
|
||||
switch (eid) {
|
||||
case ElementId::TrackNumber:
|
||||
trackId = readUint(esize);
|
||||
break;
|
||||
case ElementId::TrackType:
|
||||
if (readUint8() != 0x02) { // allow only audio tracks
|
||||
skipTo(end);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case ElementId::CodecID:
|
||||
trackCodecId = static_cast<char *>(malloc(esize + 1));
|
||||
trackCodecId[esize] = '\0';
|
||||
readBytes((uint8_t *)trackCodecId, esize);
|
||||
break;
|
||||
case ElementId::CodecPrivate:
|
||||
trackCodecPrivate = static_cast<uint8_t *>(malloc(esize));
|
||||
trackCodecPrivateLen = esize;
|
||||
readBytes(trackCodecPrivate, esize);
|
||||
break;
|
||||
case ElementId::Audio:
|
||||
continue;
|
||||
case ElementId::SamplingFrequency:
|
||||
trackRate = (uint32_t)readFloat(esize);
|
||||
break;
|
||||
case ElementId::Channels:
|
||||
trackChannels = readUint(esize);
|
||||
break;
|
||||
case ElementId::BitDepth:
|
||||
trackBits = readUint(esize);
|
||||
break;
|
||||
default:
|
||||
skipBytes(esize);
|
||||
}
|
||||
} while (pos < end);
|
||||
// not-audio tracks do not even get to this point
|
||||
audioTrackId = trackId;
|
||||
sampleRate = trackRate;
|
||||
channelCount = trackChannels;
|
||||
bitDepth = trackBits;
|
||||
codecId = trackCodecId;
|
||||
codecPrivate = trackCodecPrivate;
|
||||
codecPrivateLen = trackCodecPrivateLen;
|
||||
}
|
||||
|
||||
void WebmContainer::parseCuePoint(uint16_t idx, uint32_t end, uint32_t segmentStart) {
|
||||
CuePoint *cue = cues + idx;
|
||||
do {
|
||||
readElem();
|
||||
switch (eid) {
|
||||
case ElementId::CueTime:
|
||||
cue->time = readUint(esize);
|
||||
break;
|
||||
case ElementId::CueTrackPositions:
|
||||
continue;
|
||||
case ElementId::CueClusterPosition:
|
||||
cue->offset = segmentStart + readUint(esize);
|
||||
break;
|
||||
default:
|
||||
skipBytes(esize);
|
||||
}
|
||||
} while (pos < end);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) Kuba Szczodrzyński 2022-1-16.
|
||||
|
||||
#include "WebmContainer.h"
|
||||
|
||||
uint32_t WebmContainer::readVarNum32(bool raw) {
|
||||
uint32_t result = readUint8();
|
||||
if (!result) {
|
||||
closed = true;
|
||||
return 0;
|
||||
}
|
||||
uint8_t len = 0;
|
||||
for (; !(result >> (7 - len)); len++) {}
|
||||
if (!raw)
|
||||
result &= ~(1 << (7 - len));
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
result <<= 8;
|
||||
result |= readUint8();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t WebmContainer::readVarNum64() {
|
||||
uint64_t result = readUint8();
|
||||
if (!result) {
|
||||
closed = true;
|
||||
return 0;
|
||||
}
|
||||
uint8_t len = 0;
|
||||
for (; !(result >> (7 - len)); len++) {}
|
||||
result &= ~(1 << (7 - len));
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
result <<= 8;
|
||||
result |= readUint8();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t WebmContainer::readUint(uint8_t len) {
|
||||
if (len >= 4) {
|
||||
skipBytes(len - 4);
|
||||
return readUint32();
|
||||
}
|
||||
if (len == 3)
|
||||
return readUint24();
|
||||
if (len == 2)
|
||||
return readUint16();
|
||||
return readUint8();
|
||||
}
|
||||
|
||||
uint64_t WebmContainer::readUlong(uint8_t len) {
|
||||
if (len == 8)
|
||||
return readUint64();
|
||||
return readUint(len);
|
||||
}
|
||||
|
||||
float WebmContainer::readFloat(uint8_t len) {
|
||||
double result = 0;
|
||||
auto *b = (uint8_t *)&result;
|
||||
for (uint8_t i = 0; i < len; i++)
|
||||
b[len - i - 1] = readUint8();
|
||||
return (float)result;
|
||||
}
|
||||
|
||||
void WebmContainer::readElem() {
|
||||
eid = (ElementId)readVarNum32(true);
|
||||
esize = readVarNum32();
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "ES8311AudioSink.h"
|
||||
extern "C" {
|
||||
#include "es8311.h"
|
||||
}
|
||||
ES8311AudioSink::ES8311AudioSink()
|
||||
{
|
||||
this->softwareVolumeControl = false;
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
Es8311Config cfg = {
|
||||
.esMode = ES_MODE_SLAVE,
|
||||
.i2c_port_num = I2C_NUM_0,
|
||||
.i2c_cfg = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = 1,
|
||||
.scl_io_num = 2,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
},
|
||||
.dacOutput = (DacOutput) (DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2),
|
||||
.adcInput = ADC_INPUT_LINPUT1_RINPUT1,
|
||||
};
|
||||
cfg.i2c_cfg.master.clk_speed = 100000;
|
||||
Es8311Init(&cfg);
|
||||
Es8311SetBitsPerSample(ES_MODULE_DAC, BIT_LENGTH_16BITS);
|
||||
Es8311ConfigFmt(ES_MODULE_DAC, ES_I2S_NORMAL);
|
||||
Es8311SetVoiceVolume(60);
|
||||
Es8311Start(ES_MODULE_DAC);
|
||||
ES8311WriteReg(ES8311_CLK_MANAGER_REG01, 0xbf);
|
||||
ES8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x18);
|
||||
|
||||
// .codec_mode = AUDIO_HAL_CODEC_MODE_DECODE,
|
||||
// .i2s_iface = {
|
||||
// .mode = AUDIO_HAL_MODE_SLAVE,
|
||||
// .fmt = AUDIO_HAL_I2S_NORMAL,
|
||||
// .samples = AUDIO_HAL_44K_SAMPLES,
|
||||
// .bits = AUDIO_HAL_BIT_LENGTH_16BITS,
|
||||
// },
|
||||
// };
|
||||
|
||||
// ret_val |= es8311_codec_init(&cfg);
|
||||
// ret_val |= es8311_set_bits_per_sample(cfg.i2s_iface.bits);
|
||||
// ret_val |= es8311_config_fmt((es_i2s_fmt_t) cfg.i2s_iface.fmt);
|
||||
// ret_val |= es8311_codec_set_voice_volume(60);
|
||||
// ret_val |= es8311_codec_ctrl_state(cfg.codec_mode, AUDIO_HAL_CTRL_START);
|
||||
// ret_val |= es8311_codec_set_clk();
|
||||
|
||||
i2s_config_t i2s_config = {
|
||||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX
|
||||
.sample_rate = 44100,
|
||||
.bits_per_sample = (i2s_bits_per_sample_t)16,
|
||||
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 2-channels
|
||||
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S,
|
||||
.intr_alloc_flags = 0, // Default interrupt priority
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = 512,
|
||||
.use_apll = false,
|
||||
.tx_desc_auto_clear = true, // Auto clear tx descriptor on underflow
|
||||
.fixed_mclk = 256 * 44100};
|
||||
|
||||
i2s_pin_config_t pin_config = {
|
||||
.mck_io_num = 42,
|
||||
.bck_io_num = 40,
|
||||
.ws_io_num = 41,
|
||||
.data_out_num = 39,
|
||||
.data_in_num = -1,
|
||||
};
|
||||
|
||||
int err;
|
||||
|
||||
err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE("OI", "i2s driver installation error: %d", err);
|
||||
}
|
||||
|
||||
err = i2s_set_pin((i2s_port_t)0, &pin_config);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE("OI", "i2s set pin error: %d", err);
|
||||
}
|
||||
|
||||
// PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
// REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0);
|
||||
// ESP_LOGI("OI", "MCLK output on CLK_OUT1");
|
||||
|
||||
startI2sFeed();
|
||||
}
|
||||
|
||||
void ES8311AudioSink::volumeChanged(uint16_t volume)
|
||||
{
|
||||
// int volAdj = (int)((((float)volume) / 65536) * 100);
|
||||
// es8311_codec_set_voice_vo0lume(volAdj);
|
||||
if (volume > 79) {
|
||||
volume = 79;
|
||||
}
|
||||
Es8311SetVoiceVolume(volume);
|
||||
}
|
||||
|
||||
void ES8311AudioSink::writeReg(uint8_t reg_add, uint8_t data)
|
||||
{
|
||||
}
|
||||
|
||||
ES8311AudioSink::~ES8311AudioSink()
|
||||
{
|
||||
}
|
||||
@@ -51,11 +51,6 @@ ES8388AudioSink::ES8388AudioSink()
|
||||
ESP_LOGE("OI", "i2s set pin error: %d", err);
|
||||
}
|
||||
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0);
|
||||
ESP_LOGI("OI", "MCLK output on CLK_OUT1");
|
||||
|
||||
|
||||
err = i2c_param_config(0, &i2c_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE("OI", "i2c param config error: %d", err);
|
||||
@@ -28,10 +28,6 @@ ES9018AudioSink::ES9018AudioSink()
|
||||
i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
|
||||
i2s_set_pin((i2s_port_t)0, &pin_config);
|
||||
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0);
|
||||
ESP_LOGI("OI", "MCLK output on CLK_OUT1");
|
||||
|
||||
startI2sFeed();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ InternalAudioSink::InternalAudioSink()
|
||||
{
|
||||
softwareVolumeControl = true;
|
||||
usign = true;
|
||||
#ifdef I2S_MODE_DAC_BUILT_IN
|
||||
|
||||
i2s_config_t i2s_config = {
|
||||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), // Only TX
|
||||
@@ -24,6 +25,7 @@ InternalAudioSink::InternalAudioSink()
|
||||
i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
|
||||
//init DAC
|
||||
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
|
||||
#endif
|
||||
|
||||
startI2sFeed();
|
||||
}
|
||||
@@ -44,10 +44,6 @@ TAS5711AudioSink::TAS5711AudioSink()
|
||||
i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
|
||||
i2s_set_pin((i2s_port_t)0, &pin_config);
|
||||
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0);
|
||||
ESP_LOGI("OI", "MCLK output on CLK_OUT1");
|
||||
|
||||
// configure i2c
|
||||
i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
798
components/spotify/cspot/bell/src/audio/sinks/esp/es8311.c
Normal file
798
components/spotify/cspot/bell/src/audio/sinks/esp/es8311.c
Normal file
@@ -0,0 +1,798 @@
|
||||
/*
|
||||
* ESPRESSIF MIT License
|
||||
*
|
||||
* Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
|
||||
*
|
||||
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
|
||||
* it is free of charge, to any person obtaining a copy of this software and associated
|
||||
* documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
|
||||
* to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "es8311.h"
|
||||
// #include "board.h"
|
||||
|
||||
/* ES8311 address
|
||||
* 0x32:CE=1;0x30:CE=0
|
||||
*/
|
||||
#define ES8311_ADDR 0x32
|
||||
|
||||
#define ES7243_ADDR 0x26
|
||||
|
||||
/*
|
||||
* to define the clock soure of MCLK
|
||||
*/
|
||||
#define FROM_MCLK_PIN 0
|
||||
#define FROM_SCLK_PIN 1
|
||||
/*
|
||||
* to define work mode(master or slave)
|
||||
*/
|
||||
#define MASTER_MODE 0
|
||||
#define SLAVE_MODE 1
|
||||
/*
|
||||
* to define serial digital audio format
|
||||
*/
|
||||
#define I2S_FMT 0
|
||||
#define LEFT_JUSTIFIED_FMT 1
|
||||
#define DPS_PCM_A_FMT 2
|
||||
#define DPS_PCM_B_FMT 3
|
||||
/*
|
||||
* to define resolution of PCM interface
|
||||
*/
|
||||
#define LENGTH_16BIT 0
|
||||
#define LENGTH_24BIT 1
|
||||
#define LENGTH_32BIT 2
|
||||
|
||||
/*
|
||||
* codec private data
|
||||
*/
|
||||
struct es8311_private {
|
||||
bool dmic_enable;
|
||||
bool mclkinv;
|
||||
bool sclkinv;
|
||||
uint8_t master_slave_mode;
|
||||
uint8_t pcm_format;
|
||||
uint8_t pcm_resolution;
|
||||
uint8_t mclk_src;
|
||||
};
|
||||
static struct es8311_private *es8311_priv;
|
||||
|
||||
/*
|
||||
* Clock coefficient structer
|
||||
*/
|
||||
struct _coeff_div {
|
||||
uint32_t mclk; /* mclk frequency */
|
||||
uint32_t rate; /* sample rate */
|
||||
uint8_t prediv; /* the pre divider with range from 1 to 8 */
|
||||
uint8_t premulti; /* the pre multiplier with x1, x2, x4 and x8 selection */
|
||||
uint8_t adcdiv; /* adcclk divider */
|
||||
uint8_t dacdiv; /* dacclk divider */
|
||||
uint8_t fsmode; /* double speed or single speed, =0, ss, =1, ds */
|
||||
uint8_t lrck_h; /* adclrck divider and daclrck divider */
|
||||
uint8_t lrck_l;
|
||||
uint8_t bclkdiv; /* sclk divider */
|
||||
uint8_t adcosr; /* adc osr */
|
||||
uint8_t dacosr; /* dac osr */
|
||||
};
|
||||
/* codec hifi mclk clock divider coefficients */
|
||||
static const struct _coeff_div coeff_div[] = {
|
||||
//mclk rate prediv mult adcdiv dacdiv fsmode lrch lrcl bckdiv osr
|
||||
/* 8k */
|
||||
{12288000, 8000 , 0x06, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{18432000, 8000 , 0x03, 0x02, 0x03, 0x03, 0x00, 0x05, 0xff, 0x18, 0x10, 0x10},
|
||||
{16384000, 8000 , 0x08, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{8192000 , 8000 , 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{6144000 , 8000 , 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{4096000 , 8000 , 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{3072000 , 8000 , 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{2048000 , 8000 , 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1536000 , 8000 , 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1024000 , 8000 , 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
|
||||
/* 11.025k */
|
||||
{11289600, 11025, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{5644800 , 11025, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{2822400 , 11025, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1411200 , 11025, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
|
||||
/* 12k */
|
||||
{12288000, 12000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{6144000 , 12000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{3072000 , 12000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1536000 , 12000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
|
||||
/* 16k */
|
||||
{12288000, 16000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{18432000, 16000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x10},
|
||||
{16384000, 16000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{8192000 , 16000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{6144000 , 16000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{4096000 , 16000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{3072000 , 16000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{2048000 , 16000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1536000 , 16000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1024000 , 16000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
|
||||
/* 22.05k */
|
||||
{11289600, 22050, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{5644800 , 22050, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{2822400 , 22050, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1411200 , 22050, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
|
||||
/* 24k */
|
||||
{12288000, 24000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{18432000, 24000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{6144000 , 24000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{3072000 , 24000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1536000 , 24000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
|
||||
/* 32k */
|
||||
{12288000, 32000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{18432000, 32000, 0x03, 0x04, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x10},
|
||||
{16384000, 32000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{8192000 , 32000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{6144000 , 32000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{4096000 , 32000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{3072000 , 32000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{2048000 , 32000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1536000 , 32000, 0x03, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10},
|
||||
{1024000 , 32000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
|
||||
/* 44.1k */
|
||||
{11289600, 44100, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{5644800 , 44100, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{2822400 , 44100, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1411200 , 44100, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
|
||||
/* 48k */
|
||||
{12288000, 48000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{18432000, 48000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{6144000 , 48000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{3072000 , 48000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1536000 , 48000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
|
||||
/* 64k */
|
||||
{12288000, 64000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{18432000, 64000, 0x03, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10},
|
||||
{16384000, 64000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{8192000 , 64000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{6144000 , 64000, 0x01, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10},
|
||||
{4096000 , 64000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{3072000 , 64000, 0x01, 0x08, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10},
|
||||
{2048000 , 64000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1536000 , 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0xbf, 0x03, 0x18, 0x18},
|
||||
{1024000 , 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10},
|
||||
|
||||
/* 88.2k */
|
||||
{11289600, 88200, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{5644800 , 88200, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{2822400 , 88200, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1411200 , 88200, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10},
|
||||
|
||||
/* 96k */
|
||||
{12288000, 96000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{18432000, 96000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{6144000 , 96000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{3072000 , 96000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
|
||||
{1536000 , 96000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10},
|
||||
};
|
||||
|
||||
static char *TAG = "DRV8311";
|
||||
|
||||
#define ES_ASSERT(a, format, b, ...) \
|
||||
if ((a) != 0) { \
|
||||
ESP_LOGE(TAG, format, ##__VA_ARGS__); \
|
||||
return b;\
|
||||
}
|
||||
|
||||
static int Es8311WriteReg(uint8_t regAdd, uint8_t data)
|
||||
{
|
||||
int res = 0;
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
res |= i2c_master_start(cmd);
|
||||
res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_stop(cmd);
|
||||
res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
ES_ASSERT(res, "Es8311 Write Reg error", -1);
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311ReadReg(uint8_t regAdd)
|
||||
{
|
||||
uint8_t data;
|
||||
int res = 0;
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
|
||||
res |= i2c_master_start(cmd);
|
||||
res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_stop(cmd);
|
||||
res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
cmd = i2c_cmd_link_create();
|
||||
res |= i2c_master_start(cmd);
|
||||
res |= i2c_master_write_byte(cmd, ES8311_ADDR | 0x01, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/);
|
||||
res |= i2c_master_stop(cmd);
|
||||
res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
ES_ASSERT(res, "Es8311 Read Reg error", -1);
|
||||
return (int)data;
|
||||
}
|
||||
|
||||
static int Es7243WriteReg(uint8_t regAdd, uint8_t data)
|
||||
{
|
||||
int res = 0;
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
res |= i2c_master_start(cmd);
|
||||
res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_stop(cmd);
|
||||
res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
ES_ASSERT(res, "Es7243 Write Reg error", -1);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
int Es7243ReadReg(uint8_t regAdd)
|
||||
{
|
||||
uint8_t data;
|
||||
int res = 0;
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
|
||||
res |= i2c_master_start(cmd);
|
||||
res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_stop(cmd);
|
||||
res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
cmd = i2c_cmd_link_create();
|
||||
res |= i2c_master_start(cmd);
|
||||
res |= i2c_master_write_byte(cmd, ES7243_ADDR | 0x01, 1 /*ACK_CHECK_EN*/);
|
||||
res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/);
|
||||
res |= i2c_master_stop(cmd);
|
||||
res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
ES_ASSERT(res, "Es7243 Read Reg error", -1);
|
||||
return (int)data;
|
||||
}
|
||||
|
||||
esp_err_t Es7243Init(void)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ret |= Es7243WriteReg(0x00, 0x01);
|
||||
ret |= Es7243WriteReg(0x06, 0x00);
|
||||
ret |= Es7243WriteReg(0x05, 0x1B);
|
||||
ret |= Es7243WriteReg(0x01, 0x0C);
|
||||
ret |= Es7243WriteReg(0x08, 0x43);
|
||||
ret |= Es7243WriteReg(0x05, 0x13);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Es7243 initialize failed!");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int I2cInit(i2c_config_t *conf, int i2cMasterPort)
|
||||
{
|
||||
int res;
|
||||
res = i2c_param_config(i2cMasterPort, conf);
|
||||
res |= i2c_driver_install(i2cMasterPort, conf->mode, 0, 0, 0);
|
||||
ES_ASSERT(res, "I2cInit error", -1);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* look for the coefficient in coeff_div[] table
|
||||
*/
|
||||
static int get_coeff(uint32_t mclk, uint32_t rate)
|
||||
{
|
||||
for (int i = 0; i < (sizeof(coeff_div) / sizeof(coeff_div[0])); i++) {
|
||||
if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* set es8311 clock parameter and PCM/I2S interface
|
||||
*/
|
||||
static void es8311_pcm_hw_params(uint32_t mclk, uint32_t lrck)
|
||||
{
|
||||
int coeff;
|
||||
uint8_t regv, datmp;
|
||||
ESP_LOGI(TAG, "Enter into es8311_pcm_hw_params()\n");
|
||||
coeff = get_coeff(mclk, lrck);
|
||||
if (coeff < 0) {
|
||||
ESP_LOGE(TAG, "Unable to configure sample rate %dHz with %dHz MCLK\n", lrck, mclk);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* set clock parammeters
|
||||
*/
|
||||
if (coeff >= 0) {
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG02) & 0x07;
|
||||
regv |= (coeff_div[coeff].prediv - 1) << 5;
|
||||
datmp = 0;
|
||||
switch (coeff_div[coeff].premulti) {
|
||||
case 1:
|
||||
datmp = 0;
|
||||
break;
|
||||
case 2:
|
||||
datmp = 1;
|
||||
break;
|
||||
case 4:
|
||||
datmp = 2;
|
||||
break;
|
||||
case 8:
|
||||
datmp = 3;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#if CONFIG_ESP32_KORVO_V1_1_BOARD
|
||||
datmp = 3;
|
||||
#endif
|
||||
regv |= (datmp) << 3;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG02, regv);
|
||||
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG05) & 0x00;
|
||||
regv |= (coeff_div[coeff].adcdiv - 1) << 4;
|
||||
regv |= (coeff_div[coeff].dacdiv - 1) << 0;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG05, regv);
|
||||
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG03) & 0x80;
|
||||
regv |= coeff_div[coeff].fsmode << 6;
|
||||
regv |= coeff_div[coeff].adcosr << 0;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG03, regv);
|
||||
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG04) & 0x80;
|
||||
regv |= coeff_div[coeff].dacosr << 0;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG04, regv);
|
||||
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG07) & 0xC0;
|
||||
regv |= coeff_div[coeff].lrck_h << 0;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG07, regv);
|
||||
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG08) & 0x00;
|
||||
regv |= coeff_div[coeff].lrck_l << 0;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG08, regv);
|
||||
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06) & 0xE0;
|
||||
if (coeff_div[coeff].bclkdiv < 19) {
|
||||
regv |= (coeff_div[coeff].bclkdiv - 1) << 0;
|
||||
} else {
|
||||
regv |= (coeff_div[coeff].bclkdiv) << 0;
|
||||
}
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* set data and clock in tri-state mode
|
||||
* if tristate = 0, tri-state is disabled for normal mode
|
||||
* if tristate = 1, tri-state is enabled
|
||||
*/
|
||||
// static void es8311_set_tristate(int tristate)
|
||||
// {
|
||||
// uint8_t regv;
|
||||
// ESP_LOGI(TAG, "Enter into es8311_set_tristate(), tristate = %d\n", tristate);
|
||||
// regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG07) & 0xcf;
|
||||
// if (tristate) {
|
||||
// Es8311WriteReg(ES8311_CLK_MANAGER_REG07, regv | 0x30);
|
||||
// } else {
|
||||
// Es8311WriteReg(ES8311_CLK_MANAGER_REG07, regv);
|
||||
// }
|
||||
// }
|
||||
/*
|
||||
* set es8311 dac mute or not
|
||||
* if mute = 0, dac un-mute
|
||||
* if mute = 1, dac mute
|
||||
*/
|
||||
static void es8311_mute(int mute)
|
||||
{
|
||||
uint8_t regv;
|
||||
ESP_LOGI(TAG, "Enter into es8311_mute(), mute = %d\n", mute);
|
||||
regv = Es8311ReadReg(ES8311_DAC_REG31) & 0x9f;
|
||||
if (mute) {
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG12, 0x02);
|
||||
Es8311WriteReg(ES8311_DAC_REG31, regv | 0x60);
|
||||
Es8311WriteReg(ES8311_DAC_REG32, 0x00);
|
||||
Es8311WriteReg(ES8311_DAC_REG37, 0x08);
|
||||
} else {
|
||||
Es8311WriteReg(ES8311_DAC_REG31, regv);
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* set es8311 into suspend mode
|
||||
*/
|
||||
// static void es8311_suspend(void)
|
||||
// {
|
||||
// ESP_LOGI(TAG, "Enter into es8311_suspend()\n");
|
||||
// Es8311WriteReg(ES8311_DAC_REG32, 0x00);
|
||||
// Es8311WriteReg(ES8311_ADC_REG17, 0x00);
|
||||
// Es8311WriteReg(ES8311_SYSTEM_REG0E, 0xFF);
|
||||
// Es8311WriteReg(ES8311_SYSTEM_REG12, 0x02);
|
||||
// Es8311WriteReg(ES8311_SYSTEM_REG14, 0x00);
|
||||
// Es8311WriteReg(ES8311_SYSTEM_REG0D, 0xFA);
|
||||
// Es8311WriteReg(ES8311_ADC_REG15, 0x00);
|
||||
// Es8311WriteReg(ES8311_DAC_REG37, 0x08);
|
||||
// Es8311WriteReg(ES8311_RESET_REG00, 0x00);
|
||||
// Es8311WriteReg(ES8311_RESET_REG00, 0x1F);
|
||||
// Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x30);
|
||||
// Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x00);
|
||||
// Es8311WriteReg(ES8311_GP_REG45, 0x01);
|
||||
// }
|
||||
/*
|
||||
* initialize es8311 codec
|
||||
*/
|
||||
static void es8311_init(uint32_t mclk_freq, uint32_t lrck_freq)
|
||||
{
|
||||
int regv;
|
||||
Es8311WriteReg(ES8311_GP_REG45, 0x00);
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x30);
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x00);
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG03, 0x10);
|
||||
Es8311WriteReg(ES8311_ADC_REG16, 0x24);
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG04, 0x10);
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG05, 0x00);
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG0B, 0x00);
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG0C, 0x00);
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG10, 0x1F);
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG11, 0x7F);
|
||||
Es8311WriteReg(ES8311_RESET_REG00, 0x80);
|
||||
/*
|
||||
* Set Codec into Master or Slave mode
|
||||
*/
|
||||
regv = Es8311ReadReg(ES8311_RESET_REG00);
|
||||
/* set master/slave audio interface */
|
||||
switch (es8311_priv->master_slave_mode) {
|
||||
case MASTER_MODE: /* MASTER MODE */
|
||||
ESP_LOGI(TAG, "ES8311 in Master mode\n");
|
||||
regv |= 0x40;
|
||||
break;
|
||||
case SLAVE_MODE: /* SLAVE MODE */
|
||||
ESP_LOGI(TAG, "ES8311 in Slave mode\n");
|
||||
regv &= 0xBF;
|
||||
break;
|
||||
default:
|
||||
regv &= 0xBF;
|
||||
}
|
||||
Es8311WriteReg(ES8311_RESET_REG00, regv);
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG0D, 0x01);
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x3F);
|
||||
/*
|
||||
* select clock source for internal mclk
|
||||
*/
|
||||
switch (es8311_priv->mclk_src) {
|
||||
case FROM_MCLK_PIN:
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01);
|
||||
regv &= 0x7F;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv);
|
||||
break;
|
||||
case FROM_SCLK_PIN:
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01);
|
||||
regv |= 0x80;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv);
|
||||
break;
|
||||
default:
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01);
|
||||
regv &= 0x7F;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv);
|
||||
break;
|
||||
}
|
||||
es8311_pcm_hw_params(mclk_freq, lrck_freq);
|
||||
|
||||
/*
|
||||
* mclk inverted or not
|
||||
*/
|
||||
if (es8311_priv->mclkinv == true) {
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01);
|
||||
regv |= 0x40;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv);
|
||||
} else {
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01);
|
||||
regv &= ~(0x40);
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv);
|
||||
}
|
||||
/*
|
||||
* sclk inverted or not
|
||||
*/
|
||||
if (es8311_priv->sclkinv == true) {
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06);
|
||||
regv |= 0x20;
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv);
|
||||
} else {
|
||||
regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06);
|
||||
regv &= ~(0x20);
|
||||
Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv);
|
||||
}
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG14, 0x1A);
|
||||
/*
|
||||
* pdm dmic enable or disable
|
||||
*/
|
||||
if (es8311_priv->dmic_enable == true) {
|
||||
regv = Es8311ReadReg(ES8311_SYSTEM_REG14);
|
||||
regv |= 0x40;
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG14, regv);
|
||||
} else {
|
||||
regv = Es8311ReadReg(ES8311_SYSTEM_REG14);
|
||||
regv &= ~(0x40);
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG14, regv);
|
||||
}
|
||||
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG13, 0x10);
|
||||
Es8311WriteReg(ES8311_SYSTEM_REG0E, 0x02);
|
||||
Es8311WriteReg(ES8311_ADC_REG15, 0x40);
|
||||
Es8311WriteReg(ES8311_ADC_REG1B, 0x0A);
|
||||
Es8311WriteReg(ES8311_ADC_REG1C, 0x6A);
|
||||
Es8311WriteReg(ES8311_DAC_REG37, 0x48);
|
||||
Es8311WriteReg(ES8311_GPIO_REG44, 0x08);
|
||||
Es8311WriteReg(ES8311_DAC_REG32, 0xBF);
|
||||
|
||||
#ifdef CONFIG_USE_ES7243
|
||||
Es7243Init();
|
||||
#endif
|
||||
}
|
||||
/*
|
||||
* set codec private data and initialize codec
|
||||
*/
|
||||
static void es8311_Codec_Startup(uint32_t mclk_freq, uint32_t lrck_freq)
|
||||
{
|
||||
ESP_LOGI(TAG, "Enter into es8311_Codec_Startup()\n");
|
||||
es8311_priv->dmic_enable = false;
|
||||
es8311_priv->mclkinv = false;
|
||||
es8311_priv->sclkinv = false;
|
||||
es8311_priv->pcm_format = I2S_FMT;
|
||||
es8311_priv->pcm_resolution = LENGTH_16BIT;
|
||||
es8311_priv->master_slave_mode = SLAVE_MODE;
|
||||
#ifdef CONFIG_ESP32_KORVO_V1_1_BOARD
|
||||
es8311_priv->mclk_src = FROM_SCLK_PIN;
|
||||
#else
|
||||
es8311_priv->mclk_src = FROM_MCLK_PIN;
|
||||
#endif
|
||||
|
||||
es8311_init(mclk_freq, lrck_freq);
|
||||
|
||||
ESP_LOGI(TAG, "Exit es8311_Codec_Startup()\n");
|
||||
}
|
||||
|
||||
// static int Es8311SetAdcDacVolume(int mode, int volume, int dot)
|
||||
// {
|
||||
// int res = 0;
|
||||
// if ( volume < -96 || volume > 0 ) {
|
||||
// ESP_LOGI(TAG, "Warning: volume < -96! or > 0!\n");
|
||||
// if (volume < -96) {
|
||||
// volume = -96;
|
||||
// } else {
|
||||
// volume = 0;
|
||||
// }
|
||||
// }
|
||||
// dot = (dot >= 5 ? 1 : 0);
|
||||
// return res;
|
||||
// }
|
||||
|
||||
esp_err_t Es8311GetRef(bool flag)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint8_t regv = 0;
|
||||
if (flag) {
|
||||
regv = Es8311ReadReg(ES8311_GPIO_REG44);
|
||||
regv |= 0x50;
|
||||
ret |= Es8311WriteReg(ES8311_GPIO_REG44, regv);
|
||||
} else {
|
||||
ret |= Es8311WriteReg(ES8311_GPIO_REG44, 0x08);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Es8311Init(Es8311Config *cfg)
|
||||
{
|
||||
es8311_priv = calloc(1, sizeof(struct es8311_private));
|
||||
I2cInit(&cfg->i2c_cfg, cfg->i2c_port_num); // ESP32 in master mode
|
||||
es8311_Codec_Startup(11289600, 44100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Es8311Uninit()
|
||||
{
|
||||
Es8311WriteReg(ES8311_RESET_REG00, 0x3f);
|
||||
free(es8311_priv);
|
||||
es8311_priv = NULL;
|
||||
}
|
||||
|
||||
int Es8311ConfigFmt(ESCodecModule mode, ESCodecI2SFmt fmt)
|
||||
{
|
||||
int res = 0;
|
||||
uint8_t regAdc = 0, regDac = 0;
|
||||
if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) {
|
||||
res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF);
|
||||
}
|
||||
if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) {
|
||||
res |= Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00);
|
||||
}
|
||||
regAdc = Es8311ReadReg(ES8311_SDPIN_REG09);
|
||||
regDac = Es8311ReadReg(ES8311_SDPOUT_REG0A);
|
||||
switch (fmt) {
|
||||
case ES_I2S_NORMAL:
|
||||
ESP_LOGI(TAG, "ES8311 in I2S Format");
|
||||
regAdc &= ~0x03;
|
||||
regDac &= ~0x03;
|
||||
break;
|
||||
case ES_I2S_LEFT:
|
||||
case ES_I2S_RIGHT:
|
||||
ESP_LOGI(TAG, "ES8311 in LJ Format");
|
||||
regAdc &= ~0x03;
|
||||
regAdc |= 0x01;
|
||||
regDac &= ~0x03;
|
||||
regDac |= 0x01;
|
||||
break;
|
||||
case ES_I2S_DSP:
|
||||
ESP_LOGI(TAG, "ES8311 in DSP Format");
|
||||
regAdc |= 0x03;
|
||||
regDac |= 0x03;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Not Supported Format");
|
||||
break;
|
||||
}
|
||||
res |= Es8311WriteReg(ES8311_SDPIN_REG09, regAdc);
|
||||
res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, regDac);
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311I2sConfigClock(ESCodecI2sClock cfg)
|
||||
{
|
||||
int res = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311SetBitsPerSample(ESCodecModule mode, BitsLength bitPerSample)
|
||||
{
|
||||
int res = 0;
|
||||
uint8_t reg = 0;
|
||||
int bits = (int)bitPerSample;
|
||||
|
||||
if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) {
|
||||
reg = Es8311ReadReg(ES8311_SDPIN_REG09);
|
||||
reg = reg & 0xe3;
|
||||
res |= Es8311WriteReg(ES8311_SDPIN_REG09, reg | (bits << 2));
|
||||
}
|
||||
if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) {
|
||||
reg = Es8311ReadReg(ES8311_SDPOUT_REG0A);
|
||||
reg = reg & 0xe3;
|
||||
res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, reg | (bits << 2));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311Start(ESCodecModule mode)
|
||||
{
|
||||
int res = 0;
|
||||
if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) {
|
||||
res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF);
|
||||
}
|
||||
if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) {
|
||||
res |= Es8311WriteReg(ES8311_SYSTEM_REG12, Es8311ReadReg(ES8311_SYSTEM_REG12) & 0xfd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311Stop(ESCodecModule mode)
|
||||
{
|
||||
int res = 0;
|
||||
if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) {
|
||||
res |= Es8311WriteReg(ES8311_ADC_REG17, 0x00);
|
||||
}
|
||||
if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) {
|
||||
res |= Es8311WriteReg(ES8311_SYSTEM_REG12, Es8311ReadReg(ES8311_SYSTEM_REG12) | 0x02);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311SetVoiceVolume(int volume)
|
||||
{
|
||||
int res = 0;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
} else if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
int vol = (volume) * 2550 / 1000 + 0.5;
|
||||
ESP_LOGI(TAG, "SET: volume:%d\n", vol);
|
||||
Es8311WriteReg(ES8311_DAC_REG32, vol);
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311GetVoiceVolume(int *volume)
|
||||
{
|
||||
int res = ESP_OK;
|
||||
int regv = Es8311ReadReg(ES8311_DAC_REG32);
|
||||
if (regv == ESP_FAIL) {
|
||||
*volume = 0;
|
||||
res = ESP_FAIL;
|
||||
} else {
|
||||
*volume = regv * 100 / 256;
|
||||
}
|
||||
ESP_LOGI(TAG, "GET: res:%d, volume:%d\n", regv, *volume);
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311SetVoiceMute(int enable)
|
||||
{
|
||||
int res = 0;
|
||||
ESP_LOGI(TAG, "Es8311SetVoiceMute volume:%d\n", enable);
|
||||
es8311_mute(enable);
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311GetVoiceMute(int *mute)
|
||||
{
|
||||
int res = -1;
|
||||
uint8_t reg = 0;
|
||||
res = Es8311ReadReg(ES8311_DAC_REG31);
|
||||
if (res != ESP_FAIL) {
|
||||
reg = (res & 0x20) >> 5;
|
||||
}
|
||||
*mute = reg;
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311SetMicGain(MicGain gain)
|
||||
{
|
||||
int res = 0;
|
||||
uint8_t gain_n = Es8311ReadReg(ES8311_ADC_REG16) & 0x07;
|
||||
gain_n |= gain / 6;
|
||||
res = Es8311WriteReg(ES8311_ADC_REG16, gain_n); // MIC gain scale
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311ConfigAdcInput(AdcInput input)
|
||||
{
|
||||
int res = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
int Es8311SetAdcVolume(uint8_t adc_vol)
|
||||
{
|
||||
int res = 0;
|
||||
res = Es8311WriteReg(ES8311_ADC_REG17, adc_vol); // MIC ADC Volume
|
||||
return res;
|
||||
}
|
||||
|
||||
int ES8311WriteReg(uint8_t regAdd, uint8_t data)
|
||||
{
|
||||
return Es8311WriteReg(regAdd, data);
|
||||
}
|
||||
|
||||
void Es8311ReadAll()
|
||||
{
|
||||
for (int i = 0; i < 0x4A; i++) {
|
||||
uint8_t reg = Es8311ReadReg(i);
|
||||
ets_printf("REG:%02x, %02x\n", reg, i);
|
||||
}
|
||||
}
|
||||
BIN
components/spotify/cspot/bell/src/platform/.DS_Store
vendored
BIN
components/spotify/cspot/bell/src/platform/.DS_Store
vendored
Binary file not shown.
@@ -0,0 +1,38 @@
|
||||
#include "platform/MDNSService.h"
|
||||
#include "dns_sd.h"
|
||||
#include <arpa/inet.h>
|
||||
|
||||
/**
|
||||
* MacOS implementation of MDNSService.
|
||||
* @see https://developer.apple.com/documentation/dnssd/1804733-dnsserviceregister
|
||||
**/
|
||||
void MDNSService::registerService(
|
||||
const std::string& serviceName,
|
||||
const std::string& serviceType,
|
||||
const std::string& serviceProto,
|
||||
const std::string& serviceHost,
|
||||
int servicePort,
|
||||
const std::map<std::string, std::string> txtData
|
||||
) {
|
||||
DNSServiceRef ref = NULL;
|
||||
TXTRecordRef txtRecord;
|
||||
TXTRecordCreate(&txtRecord, 0, NULL);
|
||||
for (auto& data : txtData) {
|
||||
TXTRecordSetValue(&txtRecord, data.first.c_str(), data.second.size(), data.second.c_str());
|
||||
}
|
||||
DNSServiceRegister(
|
||||
&ref, /* sdRef */
|
||||
0, /* flags */
|
||||
0, /* interfaceIndex */
|
||||
serviceName.c_str(), /* name */
|
||||
(serviceType + "." + serviceProto).c_str(), /* regType (_spotify-connect._tcp) */
|
||||
NULL, /* domain */
|
||||
NULL, /* host */
|
||||
htons(servicePort), /* port */
|
||||
TXTRecordGetLength(&txtRecord), /* txtLen */
|
||||
TXTRecordGetBytesPtr(&txtRecord), /* txtRecord */
|
||||
NULL, /* callBack */
|
||||
NULL /* context */
|
||||
);
|
||||
TXTRecordDeallocate(&txtRecord);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "platform/MDNSService.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <vector>
|
||||
#include "mdns.h"
|
||||
|
||||
/**
|
||||
* ESP32 implementation of MDNSService
|
||||
* @see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/mdns.html
|
||||
**/
|
||||
void MDNSService::registerService(
|
||||
const std::string& serviceName,
|
||||
const std::string& serviceType,
|
||||
const std::string& serviceProto,
|
||||
const std::string& serviceHost,
|
||||
int servicePort,
|
||||
const std::map<std::string, std::string> txtData
|
||||
) {
|
||||
std::vector<mdns_txt_item_t> txtItems;
|
||||
txtItems.reserve(txtData.size());
|
||||
for (auto& data : txtData) {
|
||||
mdns_txt_item_t item;
|
||||
item.key = data.first.c_str();
|
||||
item.value = data.second.c_str();
|
||||
txtItems.push_back(item);
|
||||
}
|
||||
|
||||
mdns_service_add(
|
||||
serviceName.c_str(), /* instance_name */
|
||||
serviceType.c_str(), /* service_type */
|
||||
serviceProto.c_str(), /* proto */
|
||||
servicePort, /* port */
|
||||
txtItems.data(), /* txt */
|
||||
txtItems.size() /* num_items */
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "platform/MDNSService.h"
|
||||
|
||||
/**
|
||||
* Linux implementation of MDNSService using avahi.
|
||||
* @see https://www.avahi.org/doxygen/html/
|
||||
**/
|
||||
void MDNSService::registerService(
|
||||
const std::string& serviceName,
|
||||
const std::string& serviceType,
|
||||
const std::string& serviceProto,
|
||||
const std::string& serviceHost,
|
||||
int servicePort,
|
||||
const std::map<std::string, std::string> txtData
|
||||
) {
|
||||
//TODO: add avahi stuff
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "platform/TLSSocket.h"
|
||||
|
||||
/**
|
||||
* Platform TLSSocket implementation for the openssl
|
||||
*/
|
||||
|
||||
bell::TLSSocket::TLSSocket() {
|
||||
ERR_load_crypto_strings();
|
||||
ERR_load_SSL_strings();
|
||||
OpenSSL_add_all_algorithms();
|
||||
ctx = SSL_CTX_new(SSLv23_client_method());
|
||||
}
|
||||
|
||||
void bell::TLSSocket::open(std::string host, uint16_t port) {
|
||||
|
||||
/* We'd normally set some stuff like the verify paths and
|
||||
* mode here because as things stand this will connect to
|
||||
* any server whose certificate is signed by any CA.
|
||||
*/
|
||||
|
||||
sbio = BIO_new_ssl_connect(ctx);
|
||||
|
||||
BIO_get_ssl(sbio, &ssl);
|
||||
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
|
||||
|
||||
BELL_LOG(info, "http_tls", "Connecting with %s", host.c_str());
|
||||
BIO_set_conn_hostname(sbio, std::string(host + ":" + std::to_string(port)).c_str());
|
||||
|
||||
out = BIO_new_fp(stdout, BIO_NOCLOSE);
|
||||
if (BIO_do_connect(sbio) <= 0) {
|
||||
BELL_LOG(error, "http_tls", "Error connecting with server");
|
||||
/* whatever ... */
|
||||
}
|
||||
|
||||
if (BIO_do_handshake(sbio) <= 0) {
|
||||
|
||||
BELL_LOG(error, "http_tls", "Error TLS connection");
|
||||
|
||||
/* whatever ... */
|
||||
} // remove https or http from url
|
||||
|
||||
// split by first "/" in url
|
||||
}
|
||||
|
||||
size_t bell::TLSSocket::read(uint8_t *buf, size_t len) {
|
||||
return BIO_read(sbio, buf, len);
|
||||
}
|
||||
|
||||
size_t bell::TLSSocket::write(uint8_t *buf, size_t len) {
|
||||
return BIO_write(sbio, buf, len);
|
||||
}
|
||||
|
||||
size_t bell::TLSSocket::poll() {
|
||||
return BIO_pending(sbio);
|
||||
}
|
||||
|
||||
void bell::TLSSocket::close() {
|
||||
if (!isClosed) {
|
||||
BIO_free_all(sbio);
|
||||
BIO_free(out);
|
||||
isClosed = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "platform/WrappedSemaphore.h"
|
||||
|
||||
WrappedSemaphore::WrappedSemaphore(int count)
|
||||
{
|
||||
this->semaphoreHandle = CreateSemaphore(NULL, 0, count, NULL);
|
||||
}
|
||||
|
||||
WrappedSemaphore::~WrappedSemaphore()
|
||||
{
|
||||
CloseHandle(this->semaphoreHandle);
|
||||
}
|
||||
|
||||
int WrappedSemaphore::wait()
|
||||
{
|
||||
WaitForSingleObject(this->semaphoreHandle, INFINITE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WrappedSemaphore::twait(long milliseconds)
|
||||
{
|
||||
return WaitForSingleObject(this->semaphoreHandle, milliseconds) != WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
void WrappedSemaphore::give()
|
||||
{
|
||||
ReleaseSemaphore(this->semaphoreHandle, 1, NULL);
|
||||
}
|
||||
Reference in New Issue
Block a user