blob: d79c1316b05979f32e15daaad40d5affecd8e14f [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "raw_video_writer.h"
#include <endian.h>
#include <fcntl.h>
#include <unistd.h>
#include <atomic>
#include <iomanip>
#include <optional>
#include <string>
namespace media {
namespace {
constexpr const char* kDefaultFilePathName = "/tmp/raw_video_writer_";
constexpr const char* kFileExtension = ".raw_video";
} // namespace
template <>
std::atomic<uint32_t> RawVideoWriter<true>::instance_count_(0u);
template <bool enabled>
RawVideoWriter<enabled>::RawVideoWriter(const char* file_name) {
if (file_name) {
file_name_ = file_name;
} else {
// mostly a comment
FX_DCHECK(file_name_.empty());
}
}
template <bool enabled>
RawVideoWriter<enabled>::~RawVideoWriter() {
if (!is_done_) {
Close();
}
}
template <bool enabled>
bool RawVideoWriter<enabled>::IsOk() {
return is_ok_;
}
template <bool enabled>
size_t RawVideoWriter<enabled>::WriteUint32BigEndian(uint32_t number) {
uint32_t to_write = htobe32(number);
// May Fail() as appropriate.
WriteData(reinterpret_cast<uint8_t*>(&to_write), sizeof(to_write));
return sizeof(to_write);
}
template <bool enabled>
size_t RawVideoWriter<enabled>::WriteUint32LittleEndian(uint32_t number) {
uint32_t to_write = htole32(number);
// May Fail() as appropriate.
WriteData(reinterpret_cast<uint8_t*>(&to_write), sizeof(to_write));
return sizeof(to_write);
}
template <bool enabled>
size_t RawVideoWriter<enabled>::WriteNv12(uint32_t width_pixels, uint32_t height_pixels,
uint32_t stride_bytes, const uint8_t* const y_base,
uint32_t uv_offset) {
// Despite the copy cost, for now let's perform the write as one big write, to
// maximize the chance of getting a frame written or not written as a unit in
// case of concurrent process exit or similar. The copy cost isn't a huge
// issue for the intended debugging purpose(s).
size_t size = height_pixels * width_pixels + height_pixels / 2 * width_pixels;
if (!y_base) {
return size;
}
if (!uv_offset) {
uv_offset = height_pixels * stride_bytes;
}
// This buffer isn't small.
auto buffer = std::make_unique<uint8_t[]>(size);
uint8_t* dst = &buffer[0];
const uint8_t* src = y_base;
// Y
for (uint32_t y_line = 0; y_line < height_pixels; y_line++) {
memcpy(dst, src, width_pixels);
dst += width_pixels;
src += stride_bytes;
}
// UV
// dest is already positioned correctly to write UV data.
// src needs to account for a non-default uv_offset.
src = y_base + uv_offset;
for (uint32_t uv_line = 0; uv_line < height_pixels / 2; uv_line++) {
memcpy(dst, src, width_pixels);
dst += width_pixels;
src += stride_bytes;
}
WriteData(&buffer[0], size);
// ~buffer
return size;
}
// Intentionally don't require IsOk().
template <bool enabled>
void RawVideoWriter<enabled>::Close() {
// Caller shouldn't call Close() / Delete() repeatedly.
if (is_done_) {
Fail();
return;
}
is_done_ = true;
if (!is_initialized_) {
// file never created, so nothing to do
FX_DCHECK(!file_.is_valid());
return;
}
if (!file_.is_valid()) {
FX_DCHECK(!is_ok_);
// Don't FX_LOGS(WARNING) again since we already did previously.
return;
}
file_.reset();
FX_LOGS(INFO) << "Closed raw video file " << std::quoted(file_name_);
// is_ok_ intentionally not modified
}
// Intentionally don't require IsOk().
template <bool enabled>
void RawVideoWriter<enabled>::Delete() {
// Caller shouldn't call Close() / Delete() repeatedly.
if (is_done_) {
Fail();
return;
}
is_done_ = true;
if (!is_initialized_) {
// file never created, so nothing to do
FX_DCHECK(!file_.is_valid());
return;
}
FX_DCHECK(!file_name_.empty() || !file_.is_valid());
if (!file_.is_valid()) {
FX_DCHECK(!is_ok_);
// Don't FX_LOGS(WARNING) again since we already did previously.
return;
}
file_.reset();
if (::unlink(file_name_.c_str()) < 0) {
FX_LOGS(WARNING) << "Could not delete " << std::quoted(file_name_);
Fail();
return;
}
FX_LOGS(INFO) << "Deleted raw video file " << std::quoted(file_name_);
}
template <bool enabled>
void RawVideoWriter<enabled>::EnsureInitialized() {
if (!is_initialized_) {
is_initialized_ = true;
// If Initialize() actually fails, it's like a write failure later - is_ok_
// gets set to false.
Initialize();
}
}
template <bool enabled>
void RawVideoWriter<enabled>::Initialize() {
if (file_name_.empty()) {
uint32_t instance_count = instance_count_.fetch_add(1);
file_name_ = kDefaultFilePathName;
file_name_ += std::to_string(instance_count) + kFileExtension;
}
file_.reset(::open(file_name_.c_str(), O_CREAT | O_WRONLY | O_TRUNC));
if (!file_.is_valid()) {
FX_LOGS(WARNING) << "::open failed for " << std::quoted(file_name_) << ", returned "
<< file_.get() << ", errno " << errno;
Fail();
return;
}
}
template <bool enabled>
void RawVideoWriter<enabled>::Fail() {
FX_LOGS(WARNING) << "RawVideoWriter<enabled>::Fail()";
is_ok_ = false;
// We intentionally don't Close() or Delete() here - a client can do a Close()
// or Delete() later without having looked at IsOk(), or might decide whether
// to Close() or Delete() depending on IsOk()'s value. The previous ::write()
// calls should be effective at ensuring the previously written data is
// preserved should this process exit before a Close() or Delete().
}
template <bool enabled>
void RawVideoWriter<enabled>::WriteData(const uint8_t* to_write, size_t size) {
EnsureInitialized();
if (!is_ok_) {
// Don't FX_LOGS() again because we already did previously.
return;
}
if (is_done_) {
FX_LOGS(WARNING) << "RawVideoWriter write requested after Close() or Delete()";
Fail();
return;
}
FX_DCHECK(file_.is_valid());
::write(file_.get(), reinterpret_cast<const void*>(to_write), size);
}
template class RawVideoWriter<true>;
} // namespace media