summaryrefslogtreecommitdiff
path: root/src/audio/audio_decoder.cpp
blob: f21fb5e0767e5d1ec168c0e8f6d824f7ffe22985 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include "audio_decoder.hpp"

#include <string.h>

#include <cstddef>
#include <cstdint>
#include <memory>

#include "freertos/FreeRTOS.h"

#include "esp_heap_caps.h"
#include "freertos/message_buffer.h"
#include "freertos/portmacro.h"

#include "audio_element.hpp"
#include "chunk.hpp"
#include "fatfs_audio_input.hpp"
#include "stream_info.hpp"

namespace audio {

static const std::size_t kSamplesPerChunk = 256;

AudioDecoder::AudioDecoder()
    : IAudioElement(),
      stream_info_({}),
      has_samples_to_send_(false),
      needs_more_input_(true) {}

AudioDecoder::~AudioDecoder() {}

auto AudioDecoder::HasUnprocessedInput() -> bool {
  return !needs_more_input_ || has_samples_to_send_;
}

auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info)
    -> cpp::result<void, AudioProcessingError> {
  stream_info_ = info;

  if (info.chunk_size) {
    chunk_reader_.emplace(*info.chunk_size);
  } else {
    // TODO.
  }

  // Reuse the existing codec if we can. This will help with gapless playback,
  // since we can potentially just continue to decode as we were before,
  // without any setup overhead.
  if (current_codec_->CanHandleFile(info.path.value_or(""))) {
    current_codec_->ResetForNewStream();
    return {};
  }

  auto result = codecs::CreateCodecForFile(*info.path);
  if (result) {
    current_codec_ = std::move(*result);
  } else {
    return cpp::fail(UNSUPPORTED_STREAM);
  }

  // TODO: defer until first header read, so we can give better info about
  // sample rate, chunk size, etc.
  auto downstream_info = StreamEvent::CreateStreamInfo(
      input_events_, std::make_unique<StreamInfo>(info));
  downstream_info->stream_info->bits_per_sample = 32;
  downstream_info->stream_info->sample_rate = 48'000;
  chunk_size_ = 128;
  downstream_info->stream_info->chunk_size = chunk_size_;

  SendOrBufferEvent(std::move(downstream_info));

  return {};
}

auto AudioDecoder::ProcessChunk(const cpp::span<std::byte>& chunk)
    -> cpp::result<size_t, AudioProcessingError> {
  if (current_codec_ == nullptr || !chunk_reader_) {
    // Should never happen, but fail explicitly anyway.
    return cpp::fail(UNSUPPORTED_STREAM);
  }

  current_codec_->SetInput(chunk_reader_->HandleNewData(chunk));

  return {};
}

auto AudioDecoder::Process() -> cpp::result<void, AudioProcessingError> {
  if (has_samples_to_send_) {
    // Writing samples is relatively quick (it's just a bunch of memcopy's), so
    // do them all at once.
    while (has_samples_to_send_ && !IsOverBuffered()) {
      auto buffer = StreamEvent::CreateChunkData(input_events_, chunk_size_);
      auto write_res =
          current_codec_->WriteOutputSamples(buffer->chunk_data.bytes);
      buffer->chunk_data.bytes =
          buffer->chunk_data.bytes.first(write_res.first);
      has_samples_to_send_ = !write_res.second;

      if (!SendOrBufferEvent(std::move(buffer))) {
        return {};
      }
    }
    // We will process the next frame during the next call to this method.
    return {};
  }

  if (!needs_more_input_) {
    auto res = current_codec_->ProcessNextFrame();
    if (res.has_error()) {
      // todo
      return {};
    }
    needs_more_input_ = res.value();
    has_samples_to_send_ = true;

    if (needs_more_input_) {
      chunk_reader_->HandleLeftovers(current_codec_->GetInputPosition());
    }
  }

  return {};
}

}  // namespace audio