• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023, Alliance for Open Media. All rights reserved
3  *
4  * This source code is subject to the terms of the BSD 3-Clause Clear License
5  * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
6  * License was not distributed with this source code in the LICENSE file, you
7  * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
8  * Alliance for Open Media Patent License 1.0 was not distributed with this
9  * source code in the PATENTS file, you can obtain it at
10  * www.aomedia.org/license/patent.
11  */
12 
13 #include "iamf/cli/wav_writer.h"
14 
15 #include <algorithm>
16 #include <cstddef>
17 #include <cstdint>
18 #include <cstdio>
19 #include <memory>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/base/nullability.h"
25 #include "absl/functional/any_invocable.h"
26 #include "absl/log/check.h"
27 #include "absl/log/log.h"
28 #include "absl/memory/memory.h"
29 #include "absl/status/status.h"
30 #include "absl/strings/str_cat.h"
31 #include "absl/types/span.h"
32 #include "iamf/cli/sample_processor_base.h"
33 #include "iamf/common/utils/macros.h"
34 #include "iamf/common/utils/sample_processing_utils.h"
35 #include "src/dsp/write_wav_file.h"
36 
37 namespace iamf_tools {
38 
39 namespace {
40 // Some audio to tactile functions return 0 on success and 1 on failure.
41 constexpr int kAudioToTactileResultFailure = 0;
42 constexpr int kAudioToTactileResultSuccess = 1;
43 
44 // This class is implemented to consume all samples without producing output
45 // samples.
46 constexpr size_t kMaxOutputSamplesPerFrame = 0;
47 
48 // Write samples for all channels.
WriteSamplesInternal(absl::Nullable<FILE * > file,size_t num_channels,int bit_depth,size_t max_num_samples_per_frame,const std::vector<uint8_t> & buffer,size_t & total_samples_accumulator)49 absl::Status WriteSamplesInternal(absl::Nullable<FILE*> file,
50                                   size_t num_channels, int bit_depth,
51                                   size_t max_num_samples_per_frame,
52                                   const std::vector<uint8_t>& buffer,
53                                   size_t& total_samples_accumulator) {
54   if (file == nullptr) {
55     // Wav writer may have been aborted.
56     return absl::FailedPreconditionError(
57         "Wav writer is not accepting samples.");
58   }
59 
60   const auto buffer_size = buffer.size();
61 
62   if (buffer_size == 0) {
63     // Nothing to write.
64     return absl::OkStatus();
65   }
66 
67   if (buffer_size % (bit_depth * num_channels / 8) != 0) {
68     return absl::InvalidArgumentError(
69         "Must write an integer number of samples.");
70   }
71 
72   // Calculate how many samples there are.
73   const int bytes_per_sample = bit_depth / 8;
74   const size_t num_total_samples = (buffer_size) / bytes_per_sample;
75   const size_t num_samples_per_channel = num_total_samples / num_channels;
76   if (num_samples_per_channel > max_num_samples_per_frame) {
77     return absl::InvalidArgumentError(
78         absl::StrCat("Too many samples per frame. The `WavWriter` is "
79                      "configured with a maximum number of "
80                      "samples per frame of: ",
81                      max_num_samples_per_frame,
82                      ". The number of samples per frame received is: ",
83                      num_samples_per_channel));
84   }
85 
86   int write_sample_result = kAudioToTactileResultFailure;
87   if (bit_depth == 16) {
88     // Arrange the input samples into an int16_t to match the expected input of
89     // `WriteWavSamples`.
90     std::vector<int16_t> samples(num_total_samples, 0);
91     for (int i = 0; i < num_total_samples * bytes_per_sample;
92          i += bytes_per_sample) {
93       samples[i / bytes_per_sample] = (buffer[i + 1] << 8) | buffer[i];
94     }
95 
96     write_sample_result = WriteWavSamples(file, samples.data(), samples.size());
97   } else if (bit_depth == 24) {
98     // Arrange the input samples into an int32_t to match the expected input of
99     // `WriteWavSamples24Bit` with the lowest byte unused.
100     std::vector<int32_t> samples(num_total_samples, 0);
101     for (int i = 0; i < num_total_samples * bytes_per_sample;
102          i += bytes_per_sample) {
103       samples[i / bytes_per_sample] =
104           (buffer[i + 2] << 24) | buffer[i + 1] << 16 | buffer[i] << 8;
105     }
106     write_sample_result =
107         WriteWavSamples24Bit(file, samples.data(), samples.size());
108   } else if (bit_depth == 32) {
109     // Arrange the input samples into an int32_t to match the expected input of
110     // `WriteWavSamples32Bit`.
111     std::vector<int32_t> samples(num_total_samples, 0);
112     for (int i = 0; i < num_total_samples * bytes_per_sample;
113          i += bytes_per_sample) {
114       samples[i / bytes_per_sample] = buffer[i + 3] << 24 |
115                                       buffer[i + 2] << 16 | buffer[i + 1] << 8 |
116                                       buffer[i];
117     }
118     write_sample_result =
119         WriteWavSamples32Bit(file, samples.data(), samples.size());
120   } else {
121     // This should never happen because the factory method would never create
122     // an object with disallowed `bit_depth_` values.
123     LOG(FATAL) << "WavWriter only supports 16, 24, and 32-bit samples; got "
124                << bit_depth;
125   }
126 
127   if (write_sample_result == kAudioToTactileResultSuccess) {
128     total_samples_accumulator += num_total_samples;
129     return absl::OkStatus();
130   }
131 
132   // It's not clear why this would happen.
133   return absl::UnknownError(
134       absl::StrCat("Error writing samples to wav file. write_sample_result= ",
135                    write_sample_result));
136 }
137 
MaybeFinalizeFile(size_t sample_rate_hz,size_t num_channels,auto & wav_header_writer,FILE * & file,size_t & total_samples_written)138 void MaybeFinalizeFile(size_t sample_rate_hz, size_t num_channels,
139                        auto& wav_header_writer, FILE*& file,
140                        size_t& total_samples_written) {
141   if (file == nullptr) {
142     return;
143   }
144 
145   // Finalize the temporary header based on the total number of samples written
146   // and close the file.
147   if (wav_header_writer) {
148     std::fseek(file, 0, SEEK_SET);
149     wav_header_writer(file, total_samples_written, sample_rate_hz,
150                       num_channels);
151   }
152   std::fclose(file);
153   file = nullptr;
154 }
155 
156 }  // namespace
157 
Create(const std::string & wav_filename,int num_channels,int sample_rate_hz,int bit_depth,size_t num_samples_per_frame,bool write_header)158 std::unique_ptr<WavWriter> WavWriter::Create(const std::string& wav_filename,
159                                              int num_channels,
160                                              int sample_rate_hz, int bit_depth,
161                                              size_t num_samples_per_frame,
162                                              bool write_header) {
163   // Open the file to write to.
164   LOG(INFO) << "Writer \"" << wav_filename << "\"";
165   auto* file = std::fopen(wav_filename.c_str(), "wb");
166   if (file == nullptr) {
167     LOG(ERROR).WithPerror() << "Error opening file \"" << wav_filename << "\"";
168     return nullptr;
169   }
170 
171   // Write a dummy header. This will be overwritten in the destructor.
172   WavHeaderWriter wav_header_writer;
173   switch (bit_depth) {
174     case 16:
175       wav_header_writer = WriteWavHeader;
176       break;
177     case 24:
178       wav_header_writer = WriteWavHeader24Bit;
179       break;
180     case 32:
181       wav_header_writer = WriteWavHeader32Bit;
182       break;
183     default:
184       LOG(WARNING) << "This implementation does not support writing "
185                    << bit_depth << "-bit wav files.";
186       std::fclose(file);
187       std::remove(wav_filename.c_str());
188       return nullptr;
189   }
190 
191   // Set to an empty writer. The emptiness can be checked to skip writing the
192   // header.
193   if (!write_header) {
194     wav_header_writer = WavHeaderWriter();
195   } else if (wav_header_writer(file, 0, sample_rate_hz, num_channels) ==
196              kAudioToTactileResultFailure) {
197     LOG(ERROR) << "Error writing header of file \"" << wav_filename << "\"";
198     return nullptr;
199   }
200 
201   return absl::WrapUnique(
202       new WavWriter(wav_filename, num_channels, sample_rate_hz, bit_depth,
203                     num_samples_per_frame, file, std::move(wav_header_writer)));
204 }
205 
~WavWriter()206 WavWriter::~WavWriter() {
207   // Finalize the header, in case the user did not call `Flush()`.
208   MaybeFinalizeFile(sample_rate_hz_, num_channels_, wav_header_writer_, file_,
209                     total_samples_written_);
210 }
211 
PushFrameDerived(absl::Span<const std::vector<int32_t>> time_channel_samples)212 absl::Status WavWriter::PushFrameDerived(
213     absl::Span<const std::vector<int32_t>> time_channel_samples) {
214   // Flatten down the serialized PCM for compatibility with the internal
215   // `WriteSamplesInternal` function.
216   const size_t num_ticks = time_channel_samples.size();
217   const size_t num_channels =
218       time_channel_samples.empty() ? 0 : time_channel_samples[0].size();
219   if (!std::all_of(
220           time_channel_samples.begin(), time_channel_samples.end(),
221           [&](const auto& tick) { return tick.size() == num_channels; })) {
222     return absl::InvalidArgumentError(
223         "All ticks must have the same number of channels.");
224   }
225 
226   std::vector<uint8_t> samples_as_pcm(num_channels * num_ticks * bit_depth_ / 8,
227                                       0);
228   size_t write_position = 0;
229   for (const auto& tick : time_channel_samples) {
230     for (const auto& channel_sample : tick) {
231       RETURN_IF_NOT_OK(WritePcmSample(channel_sample, bit_depth_,
232                                       /*big_endian=*/false,
233                                       samples_as_pcm.data(), write_position));
234     }
235   }
236 
237   return WriteSamplesInternal(file_, num_channels_, bit_depth_,
238                               max_input_samples_per_frame_, samples_as_pcm,
239                               total_samples_written_);
240 }
241 
FlushDerived()242 absl::Status WavWriter::FlushDerived() {
243   // No more samples are coming, finalize the header and close the file.
244   MaybeFinalizeFile(sample_rate_hz_, num_channels_, wav_header_writer_, file_,
245                     total_samples_written_);
246   return absl::OkStatus();
247 }
248 
WritePcmSamples(const std::vector<uint8_t> & buffer)249 absl::Status WavWriter::WritePcmSamples(const std::vector<uint8_t>& buffer) {
250   return WriteSamplesInternal(file_, num_channels_, bit_depth_,
251                               max_input_samples_per_frame_, buffer,
252                               total_samples_written_);
253 }
254 
Abort()255 void WavWriter::Abort() {
256   std::fclose(file_);
257   std::remove(filename_to_remove_.c_str());
258   file_ = nullptr;
259 }
260 
WavWriter(const std::string & filename_to_remove,int num_channels,int sample_rate_hz,int bit_depth,size_t num_samples_per_frame,FILE * file,WavHeaderWriter wav_header_writer)261 WavWriter::WavWriter(const std::string& filename_to_remove, int num_channels,
262                      int sample_rate_hz, int bit_depth,
263                      size_t num_samples_per_frame, FILE* file,
264                      WavHeaderWriter wav_header_writer)
265     : SampleProcessorBase(num_samples_per_frame, num_channels,
266                           kMaxOutputSamplesPerFrame),
267       sample_rate_hz_(sample_rate_hz),
268       bit_depth_(bit_depth),
269       total_samples_written_(0),
270       file_(file),
271       filename_to_remove_(filename_to_remove),
272       wav_header_writer_(std::move(wav_header_writer)) {}
273 
274 }  // namespace iamf_tools
275