| // 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 |