mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-08-14 14:49:47 +00:00
feat(tdeck): play received voice messages
This commit is contained in:
@@ -16,6 +16,15 @@ class TDeckAudioRuntime
|
||||
public:
|
||||
bool begin();
|
||||
void requestMessageTone(uint8_t volume_percent);
|
||||
/**
|
||||
* @brief Decodes one Codec2-1300 VMP object and writes it to the speaker.
|
||||
*
|
||||
* This runs on the VMP worker, never the LVGL task. It returns false if
|
||||
* the speaker is not ready or is currently playing a notification tone.
|
||||
*/
|
||||
bool playCodec2Voice(const uint8_t* encoded_media,
|
||||
std::size_t encoded_media_len,
|
||||
uint8_t volume_percent);
|
||||
bool isReady() const;
|
||||
|
||||
private:
|
||||
@@ -29,6 +38,7 @@ class TDeckAudioRuntime
|
||||
std::atomic<bool> ready_{false};
|
||||
std::atomic<bool> faulted_{false};
|
||||
std::atomic<uint8_t> volume_percent_{45};
|
||||
std::atomic_flag audio_busy_ = ATOMIC_FLAG_INIT;
|
||||
std::array<int16_t, kPcmSampleCount> pcm_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -75,6 +75,14 @@ class TDeckBoard : public BoardBase,
|
||||
void setMessageToneVolume(uint8_t volume_percent) override;
|
||||
uint8_t getMessageToneVolume() const override;
|
||||
|
||||
/** @brief True after the board's I2S speaker runtime is available. */
|
||||
bool isVoicePlaybackReady() const;
|
||||
|
||||
/** @brief Plays one Codec2-1300 voice-message object through the speaker. */
|
||||
bool playCodec2Voice(const uint8_t* encoded_media,
|
||||
std::size_t encoded_media_len,
|
||||
uint8_t volume_percent);
|
||||
|
||||
// LilyGo_Display
|
||||
void setRotation(uint8_t rotation) override;
|
||||
uint8_t getRotation() override;
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
#include "platform/esp/common/memory_budget.h"
|
||||
#include "platform/ui/audio/pager_notification_tone.h"
|
||||
|
||||
#include <codec2.h>
|
||||
#include <driver/i2s.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
namespace boards::tdeck
|
||||
{
|
||||
@@ -30,6 +32,33 @@ constexpr size_t kAudioDmaFloor = 16 * 1024;
|
||||
// on the original 2 KiB task stack while handling MQTT chat alerts.
|
||||
constexpr uint32_t kAudioTaskStackBytes = 4096;
|
||||
constexpr UBaseType_t kAudioTaskPriority = 2;
|
||||
constexpr uint32_t kCodec2VoiceSampleRateHz = 8000U;
|
||||
constexpr std::size_t kCodec2BytesPerFrame = 7U;
|
||||
constexpr std::size_t kCodec2FramesPerMessage = 125U;
|
||||
constexpr std::size_t kCodec2MaximumEncodedBytes =
|
||||
kCodec2BytesPerFrame * kCodec2FramesPerMessage;
|
||||
constexpr std::size_t kCodec2SamplesPerFrame = 320U;
|
||||
constexpr std::size_t kCodec2OutputChannels = 2U;
|
||||
constexpr std::size_t kCodec2PcmBytes =
|
||||
kCodec2SamplesPerFrame * kCodec2OutputChannels * sizeof(int16_t);
|
||||
// Codec2 creates several working blocks while decoding. Reserve a generous
|
||||
// PSRAM window for the complete temporary operation (PCM plus Codec2 state),
|
||||
// so playback is refused before it can squeeze display/map allocations.
|
||||
constexpr std::size_t kCodec2PsramReservation = 96U * 1024U;
|
||||
constexpr std::size_t kCodec2InternalReservation = 0U;
|
||||
constexpr std::size_t kCodec2DmaReservation = 0U;
|
||||
constexpr std::size_t kCodec2InternalFloor = 48U * 1024U;
|
||||
constexpr std::size_t kCodec2DmaFloor = 16U * 1024U;
|
||||
constexpr std::size_t kCodec2PsramFloor = 256U * 1024U;
|
||||
|
||||
void secureClear(int16_t* samples, std::size_t sample_count)
|
||||
{
|
||||
volatile int16_t* cursor = samples;
|
||||
while (cursor && sample_count-- != 0U)
|
||||
{
|
||||
*cursor++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -43,7 +72,6 @@ bool TDeckAudioRuntime::begin()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
::platform::esp::common::memory::logSnapshot("audio", "before_i2s");
|
||||
if (!::platform::esp::common::memory::admit("audio",
|
||||
kAudioInternalReservation,
|
||||
@@ -157,9 +185,139 @@ void TDeckAudioRuntime::requestMessageTone(uint8_t volume_percent)
|
||||
xTaskNotifyGive(task_);
|
||||
}
|
||||
|
||||
bool TDeckAudioRuntime::playCodec2Voice(const uint8_t* encoded_media,
|
||||
std::size_t encoded_media_len,
|
||||
uint8_t volume_percent)
|
||||
{
|
||||
if (!encoded_media || encoded_media_len == 0U ||
|
||||
encoded_media_len > kCodec2MaximumEncodedBytes ||
|
||||
encoded_media_len % kCodec2BytesPerFrame != 0U ||
|
||||
!ready_.load(std::memory_order_acquire) ||
|
||||
faulted_.load(std::memory_order_acquire))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!::platform::esp::common::memory::admit("vmp_play",
|
||||
kCodec2InternalReservation,
|
||||
kCodec2DmaReservation,
|
||||
kCodec2PsramReservation,
|
||||
kCodec2InternalFloor,
|
||||
kCodec2DmaFloor,
|
||||
kCodec2PsramFloor))
|
||||
{
|
||||
std::printf("[TDeck][Audio] voice rejected reason=memory_budget\n");
|
||||
return false;
|
||||
}
|
||||
if (audio_busy_.test_and_set(std::memory_order_acquire))
|
||||
{
|
||||
std::printf("[TDeck][Audio] voice rejected reason=audio_busy\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool complete = false;
|
||||
const uint8_t output_volume = std::min<uint8_t>(volume_percent, 100U);
|
||||
int16_t* const playback_pcm = static_cast<int16_t*>(
|
||||
heap_caps_malloc(kCodec2PcmBytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
||||
if (!playback_pcm)
|
||||
{
|
||||
audio_busy_.clear(std::memory_order_release);
|
||||
std::printf("[TDeck][Audio] voice rejected reason=psram_unavailable\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t err = i2s_set_sample_rates(kSpeakerI2sPort, kCodec2VoiceSampleRateHz);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
std::printf("[TDeck][Audio] voice rejected reason=sample_rate err=%s\n",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
else
|
||||
{
|
||||
CODEC2* const decoder = codec2_create(CODEC2_MODE_1300);
|
||||
const int sample_count = decoder ? codec2_samples_per_frame(decoder) : 0;
|
||||
const int byte_count = decoder ? codec2_bytes_per_frame(decoder) : 0;
|
||||
if (!decoder || sample_count != static_cast<int>(kCodec2SamplesPerFrame) ||
|
||||
byte_count != static_cast<int>(kCodec2BytesPerFrame))
|
||||
{
|
||||
if (decoder)
|
||||
{
|
||||
codec2_destroy(decoder);
|
||||
}
|
||||
std::printf("[TDeck][Audio] voice rejected reason=codec_failure\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
codec2_set_lpc_post_filter(decoder, 1, 0, 0.8F, 0.2F);
|
||||
complete = true;
|
||||
for (std::size_t offset = 0U; offset < encoded_media_len;
|
||||
offset += kCodec2BytesPerFrame)
|
||||
{
|
||||
codec2_decode(decoder,
|
||||
playback_pcm,
|
||||
const_cast<uint8_t*>(encoded_media + offset));
|
||||
// Expand backwards so mono samples are never overwritten before
|
||||
// each sample reaches both output channels.
|
||||
for (std::size_t sample = kCodec2SamplesPerFrame; sample != 0U;
|
||||
--sample)
|
||||
{
|
||||
const std::size_t mono_index = sample - 1U;
|
||||
int32_t output_sample = playback_pcm[mono_index];
|
||||
output_sample = output_sample * output_volume / 100;
|
||||
playback_pcm[mono_index * kCodec2OutputChannels] =
|
||||
static_cast<int16_t>(output_sample);
|
||||
playback_pcm[mono_index * kCodec2OutputChannels + 1U] =
|
||||
static_cast<int16_t>(output_sample);
|
||||
}
|
||||
|
||||
size_t bytes_written = 0U;
|
||||
err = i2s_write(kSpeakerI2sPort,
|
||||
playback_pcm,
|
||||
kCodec2PcmBytes,
|
||||
&bytes_written,
|
||||
pdMS_TO_TICKS(100));
|
||||
if (err != ESP_OK || bytes_written != kCodec2PcmBytes)
|
||||
{
|
||||
std::printf("[TDeck][Audio] voice write failed err=%s requested=%u "
|
||||
"written=%u\n",
|
||||
esp_err_to_name(err),
|
||||
static_cast<unsigned>(kCodec2PcmBytes),
|
||||
static_cast<unsigned>(bytes_written));
|
||||
complete = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
codec2_destroy(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
secureClear(playback_pcm, kCodec2PcmBytes / sizeof(*playback_pcm));
|
||||
heap_caps_free(playback_pcm);
|
||||
i2s_zero_dma_buffer(kSpeakerI2sPort);
|
||||
const esp_err_t restore_err = i2s_set_sample_rates(
|
||||
kSpeakerI2sPort,
|
||||
::platform::ui::audio::pager_notification::kPlaybackSampleRateHz);
|
||||
if (restore_err != ESP_OK)
|
||||
{
|
||||
std::printf("[TDeck][Audio] voice restore sample_rate failed err=%s\n",
|
||||
esp_err_to_name(restore_err));
|
||||
complete = false;
|
||||
faulted_.store(true, std::memory_order_release);
|
||||
ready_.store(false, std::memory_order_release);
|
||||
}
|
||||
audio_busy_.clear(std::memory_order_release);
|
||||
|
||||
std::printf("[TDeck][Audio] voice %s frames=%u bytes=%u volume=%u\n",
|
||||
complete ? "complete" : "failed",
|
||||
static_cast<unsigned>(encoded_media_len / kCodec2BytesPerFrame),
|
||||
static_cast<unsigned>(encoded_media_len),
|
||||
static_cast<unsigned>(output_volume));
|
||||
return complete;
|
||||
}
|
||||
|
||||
bool TDeckAudioRuntime::isReady() const
|
||||
{
|
||||
return ready_.load(std::memory_order_acquire);
|
||||
return ready_.load(std::memory_order_acquire) &&
|
||||
!faulted_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void TDeckAudioRuntime::taskEntry(void* context)
|
||||
@@ -181,7 +339,13 @@ void TDeckAudioRuntime::taskLoop()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (audio_busy_.test_and_set(std::memory_order_acquire))
|
||||
{
|
||||
std::printf("[TDeck][Audio] tone dropped voice_playback_active\n");
|
||||
continue;
|
||||
}
|
||||
playTone(volume_percent_.load(std::memory_order_acquire));
|
||||
audio_busy_.clear(std::memory_order_release);
|
||||
const unsigned long stack_free =
|
||||
static_cast<unsigned long>(uxTaskGetStackHighWaterMark(nullptr)) *
|
||||
sizeof(StackType_t);
|
||||
|
||||
@@ -1430,6 +1430,18 @@ uint8_t TDeckBoard::getMessageToneVolume() const
|
||||
return message_tone_volume_;
|
||||
}
|
||||
|
||||
bool TDeckBoard::isVoicePlaybackReady() const
|
||||
{
|
||||
return audio_runtime_.isReady();
|
||||
}
|
||||
|
||||
bool TDeckBoard::playCodec2Voice(const uint8_t* encoded_media,
|
||||
std::size_t encoded_media_len,
|
||||
uint8_t volume_percent)
|
||||
{
|
||||
return audio_runtime_.playCodec2Voice(encoded_media, encoded_media_len, volume_percent);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
TDeckBoard& getInstanceRef()
|
||||
|
||||
@@ -18,7 +18,14 @@
|
||||
namespace chat::voice::vmp
|
||||
{
|
||||
|
||||
// T-Deck's VMP integration is playback-only and must leave most of its PSRAM
|
||||
// available for the display and map paths. Retain only the newest two local
|
||||
// voice objects there; Pager keeps its eight-message history.
|
||||
#if defined(ARDUINO_T_DECK)
|
||||
inline constexpr std::size_t kVoiceInboxCapacity = 2U;
|
||||
#else
|
||||
inline constexpr std::size_t kVoiceInboxCapacity = 8U;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Local presentation state for a voice object.
|
||||
|
||||
+1
@@ -62,6 +62,7 @@ class PagerCodec2Audio final
|
||||
PagerCodec2Audio& operator=(const PagerCodec2Audio&) = delete;
|
||||
|
||||
[[nodiscard]] bool isSupported() const;
|
||||
[[nodiscard]] bool canCapture() const;
|
||||
|
||||
/**
|
||||
* @brief Records at most 125 Codec2 frames (exactly five seconds).
|
||||
|
||||
@@ -374,11 +374,11 @@ void AppContext::initChatRuntime(bool use_mock_adapter)
|
||||
applyNetworkLimits();
|
||||
applyPrivacyConfig();
|
||||
applyChatDefaults();
|
||||
#if defined(ARDUINO_T_LORA_PAGER)
|
||||
#if defined(ARDUINO_T_LORA_PAGER) || defined(ARDUINO_T_DECK)
|
||||
if (!::platform::esp::arduino_common::voice::vmp_session::initialize(
|
||||
getSelfNodeId(), deferred_storage_store_context_ != nullptr))
|
||||
{
|
||||
Serial.printf("[VMP] Pager voice service unavailable\n");
|
||||
Serial.printf("[VMP] voice service unavailable\n");
|
||||
::ui::chat_voice::setRuntime(nullptr);
|
||||
}
|
||||
else
|
||||
@@ -714,7 +714,7 @@ void AppContext::applyMeshConfig()
|
||||
chat::infra::isReticulumMeshProtocol(
|
||||
chat::infra::normalizeMeshProtocol(config_.mesh_protocol)));
|
||||
#endif
|
||||
#if defined(ARDUINO_T_LORA_PAGER)
|
||||
#if defined(ARDUINO_T_LORA_PAGER) || defined(ARDUINO_T_DECK)
|
||||
::platform::esp::arduino_common::voice::vmp_session::setPresentationProtocol(
|
||||
static_cast<uint8_t>(chat::infra::normalizeMeshProtocol(config_.mesh_protocol)));
|
||||
#endif
|
||||
@@ -918,7 +918,7 @@ bool AppContext::switchMeshProtocol(chat::MeshProtocol protocol, bool persist)
|
||||
{
|
||||
contact_service_->setActiveProtocol(normalized);
|
||||
}
|
||||
#if defined(ARDUINO_T_LORA_PAGER)
|
||||
#if defined(ARDUINO_T_LORA_PAGER) || defined(ARDUINO_T_DECK)
|
||||
::platform::esp::arduino_common::voice::vmp_session::invalidateContactSecretCache();
|
||||
::platform::esp::arduino_common::voice::vmp_session::setPresentationProtocol(
|
||||
static_cast<uint8_t>(normalized));
|
||||
@@ -972,7 +972,7 @@ void AppContext::getEffectiveUserInfo(char* out_long, size_t long_len,
|
||||
void AppContext::updateCoreServices()
|
||||
{
|
||||
flushConfigPersistence(millis());
|
||||
#if defined(ARDUINO_T_LORA_PAGER)
|
||||
#if defined(ARDUINO_T_LORA_PAGER) || defined(ARDUINO_T_DECK)
|
||||
// Text and VMP attachments share the deferred-storage readiness boundary.
|
||||
// After that boundary, this is a rate-limited retry only when an SD I/O
|
||||
// failure prevented the local VMP attachment snapshot from restoring.
|
||||
|
||||
@@ -192,6 +192,11 @@ bool PagerCodec2Audio::isSupported() const
|
||||
return pagerBoard() != nullptr;
|
||||
}
|
||||
|
||||
bool PagerCodec2Audio::canCapture() const
|
||||
{
|
||||
return pagerBoard() != nullptr;
|
||||
}
|
||||
|
||||
CaptureResult PagerCodec2Audio::capture(const volatile bool* stop_requested)
|
||||
{
|
||||
clearEncodedMedia();
|
||||
@@ -451,6 +456,98 @@ void PagerCodec2Audio::duplicatePlaybackToStereo()
|
||||
|
||||
} // namespace platform::esp::arduino_common::voice::vmp_audio
|
||||
|
||||
#elif defined(ARDUINO_T_DECK)
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "boards/tdeck/tdeck_board.h"
|
||||
#include "platform/esp/boards/board_runtime.h"
|
||||
|
||||
namespace platform::esp::arduino_common::voice::vmp_audio
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
using ::boards::tdeck::TDeckBoard;
|
||||
|
||||
TDeckBoard* tdeckBoard()
|
||||
{
|
||||
::platform::esp::boards::AppContextInitHandles handles;
|
||||
if (!::platform::esp::boards::tryResolveAppContextInitHandles(&handles) ||
|
||||
!handles.board)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<TDeckBoard*>(handles.board);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool PagerCodec2Audio::isSupported() const
|
||||
{
|
||||
TDeckBoard* const board = tdeckBoard();
|
||||
return board && board->isVoicePlaybackReady();
|
||||
}
|
||||
|
||||
bool PagerCodec2Audio::canCapture() const
|
||||
{
|
||||
// T-Deck v1 has a bounded speaker path here; recording and outbound VMP
|
||||
// remain unavailable until a separate microphone path is implemented.
|
||||
return false;
|
||||
}
|
||||
|
||||
CaptureResult PagerCodec2Audio::capture(const volatile bool*)
|
||||
{
|
||||
clearEncodedMedia();
|
||||
return CaptureResult::Unsupported;
|
||||
}
|
||||
|
||||
const uint8_t* PagerCodec2Audio::encodedMedia() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::size_t PagerCodec2Audio::encodedMediaSize() const
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
|
||||
bool PagerCodec2Audio::hasEncodedMedia() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void PagerCodec2Audio::clearEncodedMedia()
|
||||
{
|
||||
std::memset(encoded_media_, 0, sizeof(encoded_media_));
|
||||
encoded_media_size_ = 0U;
|
||||
}
|
||||
|
||||
PlaybackResult PagerCodec2Audio::play(const uint8_t* encoded_media,
|
||||
std::size_t encoded_media_len,
|
||||
chat::voice::vmp::Codec codec,
|
||||
uint8_t volume_percent)
|
||||
{
|
||||
if (!encoded_media || encoded_media_len == 0U ||
|
||||
encoded_media_len > kMaximumEncodedBytes ||
|
||||
encoded_media_len % kCodec2BytesPerFrame != 0U ||
|
||||
codec != chat::voice::vmp::Codec::Codec2_1300)
|
||||
{
|
||||
return PlaybackResult::InvalidMedia;
|
||||
}
|
||||
|
||||
TDeckBoard* const board = tdeckBoard();
|
||||
if (!board || !board->isVoicePlaybackReady())
|
||||
{
|
||||
return PlaybackResult::Unsupported;
|
||||
}
|
||||
return board->playCodec2Voice(encoded_media, encoded_media_len, volume_percent)
|
||||
? PlaybackResult::Complete
|
||||
: PlaybackResult::AudioBusy;
|
||||
}
|
||||
|
||||
} // namespace platform::esp::arduino_common::voice::vmp_audio
|
||||
|
||||
#else
|
||||
|
||||
namespace platform::esp::arduino_common::voice::vmp_audio
|
||||
@@ -461,6 +558,11 @@ bool PagerCodec2Audio::isSupported() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PagerCodec2Audio::canCapture() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CaptureResult PagerCodec2Audio::capture(const volatile bool*)
|
||||
{
|
||||
clearEncodedMedia();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "platform/esp/arduino_common/voice/vmp_pager_session.h"
|
||||
|
||||
#if defined(ARDUINO_T_LORA_PAGER)
|
||||
#if defined(ARDUINO_T_LORA_PAGER) || defined(ARDUINO_T_DECK)
|
||||
|
||||
#include "platform/esp/arduino_common/storage/storage_runtime.h"
|
||||
#include "platform/esp/arduino_common/voice/vmp_control_runtime.h"
|
||||
@@ -58,7 +58,15 @@ constexpr uint8_t kReadyProbeCount = 3U;
|
||||
// only for an active record/play operation and are released by vTaskDelete.
|
||||
constexpr uint32_t kOutboundTaskStackBytes = 8U * 1024U;
|
||||
constexpr UBaseType_t kOutboundTaskPriority = 4U;
|
||||
#if defined(ARDUINO_T_DECK)
|
||||
// T-Deck is playback-only: Codec2 state and PCM are PSRAM-backed and its I2S
|
||||
// output path has no Pager codec/I2C capture chain. Keep this transient worker
|
||||
// aligned with T-Deck's existing 4 KiB audio task rather than reserving the
|
||||
// Pager's capture-sized 8 KiB internal-RAM stack.
|
||||
constexpr uint32_t kPlaybackTaskStackBytes = 4U * 1024U;
|
||||
#else
|
||||
constexpr uint32_t kPlaybackTaskStackBytes = 8U * 1024U;
|
||||
#endif
|
||||
constexpr UBaseType_t kPlaybackTaskPriority = 3U;
|
||||
constexpr uint32_t kPersistentInboxRetryMs = 5000U;
|
||||
|
||||
@@ -228,7 +236,7 @@ class PagerReceiveSession final
|
||||
bool canRecordAndSend() const
|
||||
{
|
||||
if (!initialized_ || !media_ || !inbox_ready_ ||
|
||||
!media_->audio.isSupported() || !lockState())
|
||||
!media_->audio.canCapture() || !lockState())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vendored
+8
-8
@@ -10,19 +10,19 @@
|
||||
#include <math.h>
|
||||
#include "memtools.h"
|
||||
|
||||
#if defined(ARDUINO_T_LORA_PAGER)
|
||||
#if defined(ARDUINO_T_LORA_PAGER) || defined(ARDUINO_T_DECK)
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Pager's Codec2 state and DSP scratch are non-DMA data. Keep them in PSRAM
|
||||
* so the fragmented internal heap remains available for FreeRTOS stacks and
|
||||
* I2S DMA. A missing PSRAM allocation fails Codec2 creation safely instead of
|
||||
* silently consuming scarce internal SRAM.
|
||||
* Pager and T-Deck Codec2 state and DSP scratch are non-DMA data. Keep them
|
||||
* in PSRAM so the fragmented internal heap remains available for FreeRTOS
|
||||
* stacks and I2S DMA. A missing PSRAM allocation fails Codec2 creation safely
|
||||
* instead of silently consuming scarce internal SRAM.
|
||||
*/
|
||||
void* codec2_malloc(size_t size)
|
||||
{
|
||||
#if defined(ARDUINO_T_LORA_PAGER)
|
||||
#if defined(ARDUINO_T_LORA_PAGER) || defined(ARDUINO_T_DECK)
|
||||
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
#else
|
||||
return malloc(size);
|
||||
@@ -31,7 +31,7 @@ void* codec2_malloc(size_t size)
|
||||
|
||||
void* codec2_calloc(size_t nmemb, size_t size)
|
||||
{
|
||||
#if defined(ARDUINO_T_LORA_PAGER)
|
||||
#if defined(ARDUINO_T_LORA_PAGER) || defined(ARDUINO_T_DECK)
|
||||
return heap_caps_calloc(nmemb, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
#else
|
||||
return calloc(nmemb, size);
|
||||
@@ -40,7 +40,7 @@ void* codec2_calloc(size_t nmemb, size_t size)
|
||||
|
||||
void codec2_free(void* ptr)
|
||||
{
|
||||
#if defined(ARDUINO_T_LORA_PAGER)
|
||||
#if defined(ARDUINO_T_LORA_PAGER) || defined(ARDUINO_T_DECK)
|
||||
heap_caps_free(ptr);
|
||||
#else
|
||||
free(ptr);
|
||||
|
||||
Reference in New Issue
Block a user