diff --git a/src/debug_utils.h b/src/debug_utils.h index b4d3d183e9a8aba6ea35200ecd8fb8d6f5bfcd5a..eeb633c524922565329a11b324f45bcf381ed0bc 100644 --- a/src/debug_utils.h +++ b/src/debug_utils.h @@ -22,7 +22,11 @@ #include "config.h" +#include "libav_deps.h" + #include <chrono> +#include <fstream> +#include <ios> #include <ratio> #warning Debug utilities included in build @@ -53,4 +57,94 @@ private: std::chrono::time_point<Clock> start_; }; +/** + * Minimally invasive audio logger. Writes a wav file from raw PCM or AVFrame. Helps debug what goes wrong with audio. + */ +class WavWriter +{ +public: + WavWriter(std::string filename, int channels, int sampleRate, int bytesPerSample) + { + f_ = std::ofstream(filename, std::ios::binary); + f_ << "RIFF----WAVEfmt "; + write(16, 4); // no extension data + write(1, 2); // PCM integer samples + write(channels, 2); // channels + write(sampleRate, 4); // sample rate + write(sampleRate * channels * bytesPerSample, 4); // sample size + write(4, 2); // data block size + write(bytesPerSample * 8, 2); // bits per sample + dataChunk_ = f_.tellp(); + f_ << "data----"; + } + + ~WavWriter() + { + length_ = f_.tellp(); + f_.seekp(dataChunk_ + 4); + write(length_ - dataChunk_ + 8, 4); + f_.seekp(4); + write(length_ - 8, 4); + } + + template<typename Word> + void write(Word value, unsigned size) + { + for (; size; --size, value >>= 8) + f_.put(static_cast<char>(value & 0xFF)); + } + + void write(AVFrame* frame) + { + AVSampleFormat fmt = (AVSampleFormat)frame->format; + int channels = frame->channels; + int depth = av_get_bytes_per_sample(fmt); + int linesize = frame->linesize[0]; + int planar = av_sample_fmt_is_planar(fmt); + int step = (planar ? depth : depth * channels); + for (int i = 0; i < linesize; i += step) { + for (int ch = 0; ch < channels; ++ch) { + int c = (planar ? ch : 0); + int offset = (planar ? i : i + depth * ch); + writeSample(&frame->extended_data[c][offset], fmt, depth); + } + } + } + +private: + void writeSample(uint8_t* p, AVSampleFormat format, int size) + { + switch (format) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + write(*(uint8_t*)p, size); + break; + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + write(*(int16_t*)p, size); + break; + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + case AV_SAMPLE_FMT_FLT: + case AV_SAMPLE_FMT_FLTP: + // float samples are always 32 bits in FFmpeg + write(*(int32_t*)p, size); + break; + case AV_SAMPLE_FMT_S64: + case AV_SAMPLE_FMT_S64P: + case AV_SAMPLE_FMT_DBL: + case AV_SAMPLE_FMT_DBLP: + // dbl samples are always 64 bits in FFmpeg + write(*(int64_t*)p, size); + break; + default: + break; + } + } + + std::ofstream f_; + size_t dataChunk_; + size_t length_; +}; + }} // namespace ring::debug