blob: 7ade0781ce84375c294345cc63f967f332935233 [file] [log] [blame]
/*
* Copyright (C) 2004-2021 Savoir-faire Linux Inc.
*
* Author: Philippe Gorley <philippe.gorley@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "config.h"
#include "libav_deps.h"
#include "media_io_handle.h"
#include "system_codec_container.h"
#include <opendht/utils.h>
#include <chrono>
#include <cstdio>
#include <fstream>
#include <ios>
#include <ratio>
#include <string_view>
#include "logger.h"
#warning Debug utilities included in build
using Clock = std::chrono::steady_clock;
using namespace std::literals;
namespace jami {
namespace debug {
/**
* Ex:
* Timer t;
* std::this_thread::sleep_for(std::chrono::milliseconds(10));
* JAMI_DBG() << "Task took " << t.getDuration<std::chrono::nanoseconds>() << " ns";
*/
class Timer
{
public:
Timer(std::string_view name) : name_(name), start_(Clock::now()) {}
~Timer() {
print("end"sv);
}
template<class Period = std::ratio<1>>
uint64_t getDuration() const
{
auto diff = std::chrono::duration_cast<Period>(Clock::now() - start_);
return diff.count();
}
void print(std::string_view action) const {
JAMI_DBG() << name_ << ": " << action << " after " << dht::print_duration(Clock::now() - start_);
}
private:
std::string_view name_;
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, AVFrame* frame)
: format_(static_cast<AVSampleFormat>(frame->format))
, channels_(frame->channels)
, planar_(av_sample_fmt_is_planar(format_))
, depth_(av_get_bytes_per_sample(format_))
, stepPerSample_(planar_ ? depth_ : depth_ * channels_)
{
std::vector<AVSampleFormat> v {AV_SAMPLE_FMT_FLT,
AV_SAMPLE_FMT_FLTP,
AV_SAMPLE_FMT_DBL,
AV_SAMPLE_FMT_DBLP};
f_ = std::ofstream(filename, std::ios_base::out | std::ios_base::binary);
f_.imbue(std::locale::classic());
f_ << "RIFF----WAVEfmt ";
if (std::find(v.begin(), v.end(), format_) == v.end()) {
write(16, 4); // Chunk size
write(1, 2); // WAVE_FORMAT_PCM
write(frame->channels, 2);
write(frame->sample_rate, 4);
write(frame->sample_rate * depth_ * frame->channels, 4); // Bytes per second
write(depth_ * frame->channels, 2); // Multi-channel sample size
write(8 * depth_, 2); // Bits per sample
f_ << "data";
dataChunk_ = f_.tellp();
f_ << "----";
} else {
write(18, 4); // Chunk size
write(3, 2); // Non PCM data
write(frame->channels, 2);
write(frame->sample_rate, 4);
write(frame->sample_rate * depth_ * frame->channels, 4); // Bytes per second
write(depth_ * frame->channels, 2); // Multi-channel sample size
write(8 * depth_, 2); // Bits per sample
write(0, 2); // Extension size
f_ << "fact";
write(4, 4); // Chunk size
factChunk_ = f_.tellp();
f_ << "----";
f_ << "data";
dataChunk_ = f_.tellp();
f_ << "----";
}
}
~WavWriter()
{
length_ = f_.tellp();
f_.seekp(dataChunk_);
write(length_ - dataChunk_ + 4, 4); // bytes_per_sample * channels * nb_samples
f_.seekp(4);
write(length_ - 8, 4);
if (factChunk_) {
f_.seekp(factChunk_);
write((length_ - dataChunk_ + 4) / depth_, 4); // channels * nb_samples
}
f_.flush();
}
template<typename Word>
void write(Word value, unsigned size = sizeof(Word))
{
auto p = reinterpret_cast<unsigned char const*>(&value);
for (int i = 0; size; --size, ++i)
f_.put(p[i]);
}
void write(AVFrame* frame)
{
for (int c = 0; c < frame->channels; ++c) {
int offset = planar_ ? 0 : depth_ * c;
for (int i = 0; i < frame->nb_samples; ++i) {
uint8_t* p = &frame->extended_data[planar_ ? c : 0][i + offset];
switch (format_) {
case AV_SAMPLE_FMT_U8:
case AV_SAMPLE_FMT_U8P:
write<uint8_t>(*(uint8_t*) p);
break;
case AV_SAMPLE_FMT_S16:
case AV_SAMPLE_FMT_S16P:
write<int16_t>(*(int16_t*) p);
break;
case AV_SAMPLE_FMT_S32:
case AV_SAMPLE_FMT_S32P:
write<int32_t>(*(int32_t*) p);
break;
case AV_SAMPLE_FMT_S64:
case AV_SAMPLE_FMT_S64P:
write<int64_t>(*(int64_t*) p);
break;
case AV_SAMPLE_FMT_FLT:
case AV_SAMPLE_FMT_FLTP:
write<float>(*(float*) p);
break;
case AV_SAMPLE_FMT_DBL:
case AV_SAMPLE_FMT_DBLP:
write<double>(*(double*) p);
break;
default:
break;
}
}
}
f_.flush();
}
private:
std::ofstream f_;
size_t dataChunk_ {0};
size_t factChunk_ {0};
size_t length_ {0};
AVSampleFormat format_ {AV_SAMPLE_FMT_NONE};
size_t channels_ {0};
bool planar_ {false};
int depth_ {0};
int stepPerSample_ {0};
};
/**
* Minimally invasive video writer. Writes raw frames. Helps debug what goes wrong with video.
*/
class VideoWriter
{
public:
VideoWriter(const std::string& filename, AVPixelFormat format, int width, int height)
: filename_(filename)
, format_(format)
, width_(width)
, height_(height)
{
f_ = fopen(filename.c_str(), "wb");
}
// so an int (VideoFrame.format()) can be passed without casting
VideoWriter(const std::string& filename, int format, int width, int height)
: VideoWriter(filename, static_cast<AVPixelFormat>(format), width, height)
{}
~VideoWriter()
{
fclose(f_);
JAMI_DBG("Play video file with: ffplay -f rawvideo -pixel_format %s -video_size %dx%d %s",
av_get_pix_fmt_name(format_),
width_,
height_,
filename_.c_str());
}
void write(VideoFrame& frame)
{
int ret = 0;
uint8_t* buffer = nullptr;
auto f = frame.pointer();
if (format_ != f->format || width_ != f->width || height_ != f->height)
return;
int size = av_image_get_buffer_size(format_, width_, height_, 1);
buffer = reinterpret_cast<uint8_t*>(av_malloc(size));
if (!buffer) {
return;
}
if ((ret = av_image_copy_to_buffer(buffer,
size,
reinterpret_cast<const uint8_t* const*>(f->data),
reinterpret_cast<const int*>(f->linesize),
format_,
width_,
height_,
1))
< 0) {
av_freep(&buffer);
return;
}
fwrite(buffer, 1, size, f_);
av_freep(&buffer);
}
private:
FILE* f_;
std::string filename_;
AVPixelFormat format_;
int width_, height_;
};
} // namespace debug
} // namespace jami