Add support for callback file
Change-Id: Ieb116bf3f645a35601f1182ed139c59ddaab8ad8
This commit is contained in:
parent
7c5508555c
commit
ea45ce3158
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include "packager/file/callback_file.h"
|
||||||
|
|
||||||
|
#include "packager/base/logging.h"
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
|
||||||
|
CallbackFile::CallbackFile(const char* file_name, const char* mode)
|
||||||
|
: File(file_name), file_mode_(mode) {}
|
||||||
|
|
||||||
|
CallbackFile::~CallbackFile() {}
|
||||||
|
|
||||||
|
bool CallbackFile::Close() {
|
||||||
|
delete this;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t CallbackFile::Read(void* buffer, uint64_t length) {
|
||||||
|
if (!callback_params_->read_func) {
|
||||||
|
LOG(ERROR) << "Read function not defined.";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return callback_params_->read_func(name_, buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t CallbackFile::Write(const void* buffer, uint64_t length) {
|
||||||
|
if (!callback_params_->write_func) {
|
||||||
|
LOG(ERROR) << "Write function not defined.";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return callback_params_->write_func(name_, buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t CallbackFile::Size() {
|
||||||
|
LOG(INFO) << "CallbackFile does not support Size().";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackFile::Flush() {
|
||||||
|
// Do nothing on Flush.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackFile::Seek(uint64_t position) {
|
||||||
|
VLOG(1) << "CallbackFile does not support Seek().";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackFile::Tell(uint64_t* position) {
|
||||||
|
VLOG(1) << "CallbackFile does not support Tell().";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackFile::Open() {
|
||||||
|
if (file_mode_ != "r" && file_mode_ != "w" && file_mode_ != "rb" &&
|
||||||
|
file_mode_ != "wb") {
|
||||||
|
LOG(ERROR) << "CallbackFile does not support file mode " << file_mode_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ParseCallbackFileName(file_name(), &callback_params_, &name_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace shaka
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include "packager/file/file.h"
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
|
||||||
|
/// Implements CallbackFile, which delegates read/write calls to the callback
|
||||||
|
/// functions set through the file name.
|
||||||
|
class CallbackFile : public File {
|
||||||
|
public:
|
||||||
|
/// @param file_name is the callback file name, which should have callback
|
||||||
|
/// address encoded. Note that the file type prefix should be stripped
|
||||||
|
/// off already.
|
||||||
|
/// @param mode C string containing a file access mode, refer to fopen for
|
||||||
|
/// the available modes.
|
||||||
|
CallbackFile(const char* file_name, const char* mode);
|
||||||
|
|
||||||
|
/// @name File implementation overrides.
|
||||||
|
/// @{
|
||||||
|
bool Close() override;
|
||||||
|
int64_t Read(void* buffer, uint64_t length) override;
|
||||||
|
int64_t Write(const void* buffer, uint64_t length) override;
|
||||||
|
int64_t Size() override;
|
||||||
|
bool Flush() override;
|
||||||
|
bool Seek(uint64_t position) override;
|
||||||
|
bool Tell(uint64_t* position) override;
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
~CallbackFile() override;
|
||||||
|
|
||||||
|
bool Open() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CallbackFile(const CallbackFile&) = delete;
|
||||||
|
CallbackFile& operator=(const CallbackFile&) = delete;
|
||||||
|
|
||||||
|
const BufferCallbackParams* callback_params_ = nullptr;
|
||||||
|
std::string name_;
|
||||||
|
std::string file_mode_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shaka
|
|
@ -0,0 +1,166 @@
|
||||||
|
// Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include "packager/file/callback_file.h"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "packager/file/file.h"
|
||||||
|
#include "packager/file/file_closer.h"
|
||||||
|
|
||||||
|
using testing::_;
|
||||||
|
using testing::Eq;
|
||||||
|
using testing::Invoke;
|
||||||
|
using testing::MockFunction;
|
||||||
|
using testing::Return;
|
||||||
|
using testing::StrEq;
|
||||||
|
using testing::WithArgs;
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const uint8_t kBuffer[] = {1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
const size_t kBufferSize = sizeof(kBuffer);
|
||||||
|
const size_t kSizeLessThanBufferSize = kBufferSize - 2;
|
||||||
|
const size_t kSizeLargerThanBufferSize = kBufferSize + 2;
|
||||||
|
const char kBufferLabel[] = "some name";
|
||||||
|
const int kFileError = -10;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(CallbackFileTest, ReadSatisfied) {
|
||||||
|
MockFunction<int64_t(const std::string& name, void* buffer, uint64_t length)>
|
||||||
|
mock_read_func;
|
||||||
|
BufferCallbackParams callback_params;
|
||||||
|
callback_params.read_func = mock_read_func.AsStdFunction();
|
||||||
|
|
||||||
|
std::string file_name =
|
||||||
|
File::MakeCallbackFileName(callback_params, kBufferLabel);
|
||||||
|
|
||||||
|
const size_t size = kSizeLessThanBufferSize;
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_read_func, Call(StrEq(kBufferLabel), _, size))
|
||||||
|
.WillOnce(WithArgs<1, 2>(Invoke([](void* buffer, uint64_t size) {
|
||||||
|
size_t size_to_copy = std::min(static_cast<size_t>(size), kBufferSize);
|
||||||
|
memcpy(buffer, kBuffer, size_to_copy);
|
||||||
|
return size_to_copy;
|
||||||
|
})));
|
||||||
|
|
||||||
|
std::unique_ptr<File, FileCloser> reader(File::Open(file_name.c_str(), "r"));
|
||||||
|
ASSERT_TRUE(reader);
|
||||||
|
uint8_t read_buffer[size];
|
||||||
|
ASSERT_EQ(static_cast<int64_t>(size), reader->Read(read_buffer, size));
|
||||||
|
EXPECT_EQ(0, memcmp(kBuffer, read_buffer, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CallbackFileTest, ReadNotSatisfied) {
|
||||||
|
MockFunction<int64_t(const std::string& name, void* buffer, uint64_t length)>
|
||||||
|
mock_read_func;
|
||||||
|
BufferCallbackParams callback_params;
|
||||||
|
callback_params.read_func = mock_read_func.AsStdFunction();
|
||||||
|
|
||||||
|
std::string file_name =
|
||||||
|
File::MakeCallbackFileName(callback_params, kBufferLabel);
|
||||||
|
|
||||||
|
const size_t size = kSizeLargerThanBufferSize;
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_read_func, Call(StrEq(kBufferLabel), _, size))
|
||||||
|
.WillOnce(WithArgs<1, 2>(Invoke([](void* buffer, uint64_t size) {
|
||||||
|
size_t size_to_copy = std::min(static_cast<size_t>(size), kBufferSize);
|
||||||
|
memcpy(buffer, kBuffer, size_to_copy);
|
||||||
|
return size_to_copy;
|
||||||
|
})));
|
||||||
|
|
||||||
|
std::unique_ptr<File, FileCloser> reader(File::Open(file_name.c_str(), "r"));
|
||||||
|
ASSERT_TRUE(reader);
|
||||||
|
uint8_t read_buffer[size];
|
||||||
|
ASSERT_EQ(static_cast<int64_t>(kBufferSize), reader->Read(read_buffer, size));
|
||||||
|
EXPECT_EQ(0, memcmp(kBuffer, read_buffer, kBufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CallbackFileTest, ReadFailed) {
|
||||||
|
MockFunction<int64_t(const std::string& name, void* buffer, uint64_t length)>
|
||||||
|
mock_read_func;
|
||||||
|
BufferCallbackParams callback_params;
|
||||||
|
callback_params.read_func = mock_read_func.AsStdFunction();
|
||||||
|
|
||||||
|
std::string file_name =
|
||||||
|
File::MakeCallbackFileName(callback_params, kBufferLabel);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_read_func, Call(StrEq(kBufferLabel), _, _))
|
||||||
|
.WillOnce(WithArgs<1, 2>(
|
||||||
|
Invoke([](void* buffer, uint64_t size) { return kFileError; })));
|
||||||
|
|
||||||
|
std::unique_ptr<File, FileCloser> reader(File::Open(file_name.c_str(), "r"));
|
||||||
|
ASSERT_TRUE(reader);
|
||||||
|
uint8_t read_buffer[kBufferSize];
|
||||||
|
ASSERT_EQ(kFileError, reader->Read(read_buffer, kBufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CallbackFileTest, ReadFunctionNotDefined) {
|
||||||
|
BufferCallbackParams callback_params;
|
||||||
|
std::string file_name =
|
||||||
|
File::MakeCallbackFileName(callback_params, kBufferLabel);
|
||||||
|
|
||||||
|
std::unique_ptr<File, FileCloser> reader(File::Open(file_name.c_str(), "r"));
|
||||||
|
ASSERT_TRUE(reader);
|
||||||
|
uint8_t read_buffer[kBufferSize];
|
||||||
|
ASSERT_EQ(-1, reader->Read(read_buffer, kBufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CallbackFileTest, WriteSatisfied) {
|
||||||
|
MockFunction<int64_t(const std::string& name, const void* buffer,
|
||||||
|
uint64_t length)>
|
||||||
|
mock_write_func;
|
||||||
|
BufferCallbackParams callback_params;
|
||||||
|
callback_params.write_func = mock_write_func.AsStdFunction();
|
||||||
|
|
||||||
|
std::string file_name =
|
||||||
|
File::MakeCallbackFileName(callback_params, kBufferLabel);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_write_func,
|
||||||
|
Call(StrEq(kBufferLabel), Eq(kBuffer), kBufferSize))
|
||||||
|
.WillOnce(Return(kBufferSize));
|
||||||
|
|
||||||
|
std::unique_ptr<File, FileCloser> writer(File::Open(file_name.c_str(), "w"));
|
||||||
|
ASSERT_TRUE(writer);
|
||||||
|
ASSERT_EQ(static_cast<int64_t>(kBufferSize),
|
||||||
|
writer->Write(kBuffer, kBufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CallbackFileTest, WriteFailed) {
|
||||||
|
MockFunction<int64_t(const std::string& name, const void* buffer,
|
||||||
|
uint64_t length)>
|
||||||
|
mock_write_func;
|
||||||
|
BufferCallbackParams callback_params;
|
||||||
|
callback_params.write_func = mock_write_func.AsStdFunction();
|
||||||
|
|
||||||
|
std::string file_name =
|
||||||
|
File::MakeCallbackFileName(callback_params, kBufferLabel);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_write_func,
|
||||||
|
Call(StrEq(kBufferLabel), Eq(kBuffer), kBufferSize))
|
||||||
|
.WillOnce(Return(kFileError));
|
||||||
|
|
||||||
|
std::unique_ptr<File, FileCloser> writer(File::Open(file_name.c_str(), "w"));
|
||||||
|
ASSERT_TRUE(writer);
|
||||||
|
ASSERT_EQ(kFileError, writer->Write(kBuffer, kBufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CallbackFileTest, WriteFunctionNotDefined) {
|
||||||
|
BufferCallbackParams callback_params;
|
||||||
|
std::string file_name =
|
||||||
|
File::MakeCallbackFileName(callback_params, kBufferLabel);
|
||||||
|
|
||||||
|
std::unique_ptr<File, FileCloser> writer(File::Open(file_name.c_str(), "w"));
|
||||||
|
ASSERT_TRUE(writer);
|
||||||
|
ASSERT_EQ(-1, writer->Write(kBuffer, kBufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace shaka
|
|
@ -7,11 +7,15 @@
|
||||||
#include "packager/file/file.h"
|
#include "packager/file/file.h"
|
||||||
|
|
||||||
#include <gflags/gflags.h>
|
#include <gflags/gflags.h>
|
||||||
|
#include <inttypes.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "packager/base/files/file_util.h"
|
#include "packager/base/files/file_util.h"
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
|
#include "packager/base/strings/string_number_conversions.h"
|
||||||
#include "packager/base/strings/string_piece.h"
|
#include "packager/base/strings/string_piece.h"
|
||||||
|
#include "packager/base/strings/stringprintf.h"
|
||||||
|
#include "packager/file/callback_file.h"
|
||||||
#include "packager/file/file_util.h"
|
#include "packager/file/file_util.h"
|
||||||
#include "packager/file/local_file.h"
|
#include "packager/file/local_file.h"
|
||||||
#include "packager/file/memory_file.h"
|
#include "packager/file/memory_file.h"
|
||||||
|
@ -33,9 +37,10 @@ DEFINE_uint64(io_block_size,
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
|
||||||
|
const char* kCallbackFilePrefix = "callback://";
|
||||||
const char* kLocalFilePrefix = "file://";
|
const char* kLocalFilePrefix = "file://";
|
||||||
const char* kUdpFilePrefix = "udp://";
|
|
||||||
const char* kMemoryFilePrefix = "memory://";
|
const char* kMemoryFilePrefix = "memory://";
|
||||||
|
const char* kUdpFilePrefix = "udp://";
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -46,12 +51,15 @@ typedef bool (*FileAtomicWriteFunction)(const char* file_name,
|
||||||
|
|
||||||
struct FileTypeInfo {
|
struct FileTypeInfo {
|
||||||
const char* type;
|
const char* type;
|
||||||
size_t type_length;
|
|
||||||
const FileFactoryFunction factory_function;
|
const FileFactoryFunction factory_function;
|
||||||
const FileDeleteFunction delete_function;
|
const FileDeleteFunction delete_function;
|
||||||
const FileAtomicWriteFunction atomic_write_function;
|
const FileAtomicWriteFunction atomic_write_function;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
File* CreateCallbackFile(const char* file_name, const char* mode) {
|
||||||
|
return new CallbackFile(file_name, mode);
|
||||||
|
}
|
||||||
|
|
||||||
File* CreateLocalFile(const char* file_name, const char* mode) {
|
File* CreateLocalFile(const char* file_name, const char* mode) {
|
||||||
return new LocalFile(file_name, mode);
|
return new LocalFile(file_name, mode);
|
||||||
}
|
}
|
||||||
|
@ -99,21 +107,26 @@ bool DeleteMemoryFile(const char* file_name) {
|
||||||
static const FileTypeInfo kFileTypeInfo[] = {
|
static const FileTypeInfo kFileTypeInfo[] = {
|
||||||
{
|
{
|
||||||
kLocalFilePrefix,
|
kLocalFilePrefix,
|
||||||
strlen(kLocalFilePrefix),
|
|
||||||
&CreateLocalFile,
|
&CreateLocalFile,
|
||||||
&DeleteLocalFile,
|
&DeleteLocalFile,
|
||||||
&WriteLocalFileAtomically,
|
&WriteLocalFileAtomically,
|
||||||
},
|
},
|
||||||
{kUdpFilePrefix, strlen(kUdpFilePrefix), &CreateUdpFile, nullptr, nullptr},
|
{kUdpFilePrefix, &CreateUdpFile, nullptr, nullptr},
|
||||||
{kMemoryFilePrefix, strlen(kMemoryFilePrefix), &CreateMemoryFile,
|
{kMemoryFilePrefix, &CreateMemoryFile, &DeleteMemoryFile, nullptr},
|
||||||
&DeleteMemoryFile, nullptr},
|
{kCallbackFilePrefix, &CreateCallbackFile, nullptr, nullptr},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
base::StringPiece GetFileTypePrefix(base::StringPiece file_name) {
|
||||||
|
size_t pos = file_name.find("://");
|
||||||
|
return (pos == std::string::npos) ? "" : file_name.substr(0, pos + 3);
|
||||||
|
}
|
||||||
|
|
||||||
const FileTypeInfo* GetFileTypeInfo(base::StringPiece file_name,
|
const FileTypeInfo* GetFileTypeInfo(base::StringPiece file_name,
|
||||||
base::StringPiece* real_file_name) {
|
base::StringPiece* real_file_name) {
|
||||||
|
base::StringPiece file_type_prefix = GetFileTypePrefix(file_name);
|
||||||
for (const FileTypeInfo& file_type : kFileTypeInfo) {
|
for (const FileTypeInfo& file_type : kFileTypeInfo) {
|
||||||
if (strncmp(file_type.type, file_name.data(), file_type.type_length) == 0) {
|
if (file_type_prefix == file_type.type) {
|
||||||
*real_file_name = file_name.substr(file_type.type_length);
|
*real_file_name = file_name.substr(file_type_prefix.size());
|
||||||
return &file_type;
|
return &file_type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,8 +141,10 @@ File* File::Create(const char* file_name, const char* mode) {
|
||||||
std::unique_ptr<File, FileCloser> internal_file(
|
std::unique_ptr<File, FileCloser> internal_file(
|
||||||
CreateInternalFile(file_name, mode));
|
CreateInternalFile(file_name, mode));
|
||||||
|
|
||||||
if (!strncmp(file_name, kMemoryFilePrefix, strlen(kMemoryFilePrefix))) {
|
base::StringPiece file_type_prefix = GetFileTypePrefix(file_name);
|
||||||
// Disable caching for memory files.
|
if (file_type_prefix == kMemoryFilePrefix ||
|
||||||
|
file_type_prefix == kCallbackFilePrefix) {
|
||||||
|
// Disable caching for memory and callback files.
|
||||||
return internal_file.release();
|
return internal_file.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,4 +343,32 @@ int64_t File::CopyFile(File* source, File* destination, int64_t max_copy) {
|
||||||
return bytes_copied;
|
return bytes_copied;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string File::MakeCallbackFileName(
|
||||||
|
const BufferCallbackParams& callback_params,
|
||||||
|
const std::string& name) {
|
||||||
|
if (name.empty())
|
||||||
|
return "";
|
||||||
|
return base::StringPrintf("%s%" PRIdPTR "/%s", kCallbackFilePrefix,
|
||||||
|
reinterpret_cast<intptr_t>(&callback_params),
|
||||||
|
name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool File::ParseCallbackFileName(const std::string& callback_file_name,
|
||||||
|
const BufferCallbackParams** callback_params,
|
||||||
|
std::string* name) {
|
||||||
|
size_t pos = callback_file_name.find("/");
|
||||||
|
int64_t callback_address = 0;
|
||||||
|
if (pos == std::string::npos ||
|
||||||
|
!base::StringToInt64(callback_file_name.substr(0, pos),
|
||||||
|
&callback_address)) {
|
||||||
|
LOG(ERROR) << "Expecting CallbackFile with name like "
|
||||||
|
"'<callback address>/<entity name>', but seeing "
|
||||||
|
<< callback_file_name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*callback_params = reinterpret_cast<BufferCallbackParams*>(callback_address);
|
||||||
|
*name = callback_file_name.substr(pos + 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
'target_name': 'file',
|
'target_name': 'file',
|
||||||
'type': '<(component)',
|
'type': '<(component)',
|
||||||
'sources': [
|
'sources': [
|
||||||
|
'callback_file.cc',
|
||||||
|
'callback_file.h',
|
||||||
'file.cc',
|
'file.cc',
|
||||||
'file.h',
|
'file.h',
|
||||||
'file_util.cc',
|
'file_util.cc',
|
||||||
|
@ -24,6 +26,7 @@
|
||||||
'local_file.h',
|
'local_file.h',
|
||||||
'memory_file.cc',
|
'memory_file.cc',
|
||||||
'memory_file.h',
|
'memory_file.h',
|
||||||
|
'public/buffer_callback_params.h',
|
||||||
'threaded_io_file.cc',
|
'threaded_io_file.cc',
|
||||||
'threaded_io_file.h',
|
'threaded_io_file.h',
|
||||||
'udp_file.cc',
|
'udp_file.cc',
|
||||||
|
@ -40,6 +43,7 @@
|
||||||
'target_name': 'file_unittest',
|
'target_name': 'file_unittest',
|
||||||
'type': '<(gtest_target_type)',
|
'type': '<(gtest_target_type)',
|
||||||
'sources': [
|
'sources': [
|
||||||
|
'callback_file_unittest.cc',
|
||||||
'file_unittest.cc',
|
'file_unittest.cc',
|
||||||
'file_util_unittest.cc',
|
'file_util_unittest.cc',
|
||||||
'io_cache_unittest.cc',
|
'io_cache_unittest.cc',
|
||||||
|
@ -48,6 +52,7 @@
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../media/test/media_test.gyp:run_tests_with_atexit_manager',
|
'../media/test/media_test.gyp:run_tests_with_atexit_manager',
|
||||||
|
'../testing/gmock.gyp:gmock',
|
||||||
'../testing/gtest.gyp:gtest',
|
'../testing/gtest.gyp:gtest',
|
||||||
'../third_party/gflags/gflags.gyp:gflags',
|
'../third_party/gflags/gflags.gyp:gflags',
|
||||||
'file',
|
'file',
|
||||||
|
|
|
@ -12,11 +12,14 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "packager/base/macros.h"
|
#include "packager/base/macros.h"
|
||||||
|
#include "packager/file/public/buffer_callback_params.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
|
||||||
|
extern const char* kCallbackFilePrefix;
|
||||||
extern const char* kLocalFilePrefix;
|
extern const char* kLocalFilePrefix;
|
||||||
extern const char* kMemoryFilePrefix;
|
extern const char* kMemoryFilePrefix;
|
||||||
|
extern const char* kUdpFilePrefix;
|
||||||
const int64_t kWholeFile = -1;
|
const int64_t kWholeFile = -1;
|
||||||
|
|
||||||
/// Define an abstract file interface.
|
/// Define an abstract file interface.
|
||||||
|
@ -86,7 +89,8 @@ class File {
|
||||||
/// @return true on succcess, false otherwise.
|
/// @return true on succcess, false otherwise.
|
||||||
virtual bool Tell(uint64_t* position) = 0;
|
virtual bool Tell(uint64_t* position) = 0;
|
||||||
|
|
||||||
/// @return The file name.
|
/// @return The file name. Note that the file type prefix has been stripped
|
||||||
|
/// off.
|
||||||
const std::string& file_name() const { return file_name_; }
|
const std::string& file_name() const { return file_name_; }
|
||||||
|
|
||||||
// ************************************************************
|
// ************************************************************
|
||||||
|
@ -138,6 +142,27 @@ class File {
|
||||||
/// @return Number of bytes written, or a value < 0 on error.
|
/// @return Number of bytes written, or a value < 0 on error.
|
||||||
static int64_t CopyFile(File* source, File* destination, int64_t max_copy);
|
static int64_t CopyFile(File* source, File* destination, int64_t max_copy);
|
||||||
|
|
||||||
|
/// Generate callback file name.
|
||||||
|
/// NOTE: THE GENERATED NAME IS ONLY VAID WHILE @a callback_params IS VALID.
|
||||||
|
/// @param callback_params references BufferCallbackParams, which will be
|
||||||
|
/// embedded in the generated callback file name.
|
||||||
|
/// @param name is the name of the buffer, which will be embedded in the
|
||||||
|
/// generated callback file name.
|
||||||
|
static std::string MakeCallbackFileName(
|
||||||
|
const BufferCallbackParams& callback_params,
|
||||||
|
const std::string& name);
|
||||||
|
|
||||||
|
/// Parse and extract callback params.
|
||||||
|
/// @param callback_file_name is the name of the callback file which contains
|
||||||
|
/// @a callback_params and @a name.
|
||||||
|
/// @param callback_params points to the parsed BufferCallbackParams pointer.
|
||||||
|
/// @param name points to the parsed name.
|
||||||
|
/// @return true on success, false otherwise.
|
||||||
|
static bool ParseCallbackFileName(
|
||||||
|
const std::string& callback_file_name,
|
||||||
|
const BufferCallbackParams** callback_params,
|
||||||
|
std::string* name);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit File(const std::string& file_name) : file_name_(file_name) {}
|
explicit File(const std::string& file_name) : file_name_(file_name) {}
|
||||||
/// Do *not* call the destructor directly (with the "delete" keyword)
|
/// Do *not* call the destructor directly (with the "delete" keyword)
|
||||||
|
@ -156,7 +181,9 @@ class File {
|
||||||
|
|
||||||
static File* CreateInternalFile(const char* file_name, const char* mode);
|
static File* CreateInternalFile(const char* file_name, const char* mode);
|
||||||
|
|
||||||
|
// Note that the file type prefix has been stripped off.
|
||||||
std::string file_name_;
|
std::string file_name_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(File);
|
DISALLOW_COPY_AND_ASSIGN(File);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -300,4 +300,27 @@ TEST_F(LocalFileTest, DISABLED_ReadSeekOutOfBounds) {
|
||||||
EXPECT_TRUE(file->Close());
|
EXPECT_TRUE(file->Close());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(FileTest, MakeCallbackFileName) {
|
||||||
|
const BufferCallbackParams* params =
|
||||||
|
reinterpret_cast<BufferCallbackParams*>(1000);
|
||||||
|
EXPECT_EQ("callback://1000/some name",
|
||||||
|
File::MakeCallbackFileName(*params, "some name"));
|
||||||
|
EXPECT_EQ("", File::MakeCallbackFileName(*params, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileTest, ParseCallbackFileName) {
|
||||||
|
const BufferCallbackParams* params = nullptr;
|
||||||
|
std::string name;
|
||||||
|
ASSERT_TRUE(File::ParseCallbackFileName("1000/some name", ¶ms, &name));
|
||||||
|
EXPECT_EQ(1000, reinterpret_cast<int64_t>(params));
|
||||||
|
EXPECT_EQ("some name", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileTest, ParseCallbackFileNameFailed) {
|
||||||
|
const BufferCallbackParams* params = nullptr;
|
||||||
|
std::string name;
|
||||||
|
ASSERT_FALSE(File::ParseCallbackFileName("1000\\some name", ¶ms, &name));
|
||||||
|
ASSERT_FALSE(File::ParseCallbackFileName("abc/some name", ¶ms, &name));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#ifndef PACKAGER_FILE_PUBLIC_BUFFER_CALLBACK_PARAMS_H_
|
||||||
|
#define PACKAGER_FILE_PUBLIC_BUFFER_CALLBACK_PARAMS_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
|
||||||
|
/// Buffer callback params.
|
||||||
|
struct BufferCallbackParams {
|
||||||
|
/// If this function is specified, packager treats @a StreamDescriptor.input
|
||||||
|
/// as a label and call this function with @a name set to
|
||||||
|
/// @a StreamDescriptor.input.
|
||||||
|
std::function<int64_t(const std::string& name, void* buffer, uint64_t size)>
|
||||||
|
read_func;
|
||||||
|
/// If this function is specified, packager treats the output files specified
|
||||||
|
/// in PackagingParams and StreamDescriptors as labels and calls this function
|
||||||
|
/// with @a name set. This applies to @a
|
||||||
|
/// PackagingParams.MpdParams.mpd_output,
|
||||||
|
/// @a PackagingParams.HlsParams.master_playlist_output, @a
|
||||||
|
/// StreamDescriptor.output, @a StreamDescriptor.segment_template, @a
|
||||||
|
/// StreamDescriptor.hls_playlist_name.
|
||||||
|
std::function<
|
||||||
|
int64_t(const std::string& name, const void* buffer, uint64_t size)>
|
||||||
|
write_func;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shaka
|
||||||
|
|
||||||
|
#endif // PACKAGER_FILE_PUBLIC_BUFFER_CALLBACK_PARAMS_H_
|
|
@ -612,6 +612,7 @@ struct Packager::PackagerInternal {
|
||||||
std::unique_ptr<MpdNotifier> mpd_notifier;
|
std::unique_ptr<MpdNotifier> mpd_notifier;
|
||||||
std::unique_ptr<hls::HlsNotifier> hls_notifier;
|
std::unique_ptr<hls::HlsNotifier> hls_notifier;
|
||||||
std::vector<std::unique_ptr<media::Job>> jobs;
|
std::vector<std::unique_ptr<media::Job>> jobs;
|
||||||
|
BufferCallbackParams buffer_callback_params;
|
||||||
};
|
};
|
||||||
|
|
||||||
Packager::Packager() {}
|
Packager::Packager() {}
|
||||||
|
@ -651,12 +652,24 @@ Status Packager::Initialize(
|
||||||
return Status(error::INVALID_ARGUMENT, "Failed to create key source.");
|
return Status(error::INVALID_ARGUMENT, "Failed to create key source.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const MpdParams& mpd_params = packaging_params.mpd_params;
|
// Store callback params to make it available during packaging.
|
||||||
|
internal->buffer_callback_params = packaging_params.buffer_callback_params;
|
||||||
|
|
||||||
|
// Update mpd output and hls output if callback param is specified.
|
||||||
|
MpdParams mpd_params = packaging_params.mpd_params;
|
||||||
|
HlsParams hls_params = packaging_params.hls_params;
|
||||||
|
if (internal->buffer_callback_params.write_func) {
|
||||||
|
mpd_params.mpd_output = File::MakeCallbackFileName(
|
||||||
|
internal->buffer_callback_params, mpd_params.mpd_output);
|
||||||
|
hls_params.master_playlist_output = File::MakeCallbackFileName(
|
||||||
|
internal->buffer_callback_params, hls_params.master_playlist_output);
|
||||||
|
}
|
||||||
|
|
||||||
if (!mpd_params.mpd_output.empty()) {
|
if (!mpd_params.mpd_output.empty()) {
|
||||||
const bool on_demand_dash_profile =
|
const bool on_demand_dash_profile =
|
||||||
stream_descriptors.begin()->segment_template.empty();
|
stream_descriptors.begin()->segment_template.empty();
|
||||||
MpdOptions mpd_options = media::GetMpdOptions(on_demand_dash_profile,
|
MpdOptions mpd_options =
|
||||||
packaging_params.mpd_params);
|
media::GetMpdOptions(on_demand_dash_profile, mpd_params);
|
||||||
if (mpd_params.generate_dash_if_iop_compliant_mpd) {
|
if (mpd_params.generate_dash_if_iop_compliant_mpd) {
|
||||||
internal->mpd_notifier.reset(new DashIopMpdNotifier(mpd_options));
|
internal->mpd_notifier.reset(new DashIopMpdNotifier(mpd_options));
|
||||||
} else {
|
} else {
|
||||||
|
@ -669,7 +682,6 @@ Status Packager::Initialize(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const HlsParams& hls_params = packaging_params.hls_params;
|
|
||||||
if (!hls_params.master_playlist_output.empty()) {
|
if (!hls_params.master_playlist_output.empty()) {
|
||||||
base::FilePath master_playlist_path(
|
base::FilePath master_playlist_path(
|
||||||
base::FilePath::FromUTF8Unsafe(hls_params.master_playlist_output));
|
base::FilePath::FromUTF8Unsafe(hls_params.master_playlist_output));
|
||||||
|
@ -683,8 +695,26 @@ Status Packager::Initialize(
|
||||||
}
|
}
|
||||||
|
|
||||||
media::StreamDescriptorList stream_descriptor_list;
|
media::StreamDescriptorList stream_descriptor_list;
|
||||||
for (const StreamDescriptor& descriptor : stream_descriptors)
|
for (const StreamDescriptor& descriptor : stream_descriptors) {
|
||||||
|
if (internal->buffer_callback_params.read_func ||
|
||||||
|
internal->buffer_callback_params.write_func) {
|
||||||
|
StreamDescriptor descriptor_copy = descriptor;
|
||||||
|
if (internal->buffer_callback_params.read_func) {
|
||||||
|
descriptor_copy.input = File::MakeCallbackFileName(
|
||||||
|
internal->buffer_callback_params, descriptor.input);
|
||||||
|
}
|
||||||
|
if (internal->buffer_callback_params.write_func) {
|
||||||
|
descriptor_copy.output = File::MakeCallbackFileName(
|
||||||
|
internal->buffer_callback_params, descriptor.output);
|
||||||
|
descriptor_copy.segment_template = File::MakeCallbackFileName(
|
||||||
|
internal->buffer_callback_params, descriptor.segment_template);
|
||||||
|
}
|
||||||
|
stream_descriptor_list.insert(descriptor_copy);
|
||||||
|
} else {
|
||||||
stream_descriptor_list.insert(descriptor);
|
stream_descriptor_list.insert(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Status status = media::CreateRemuxJobs(
|
Status status = media::CreateRemuxJobs(
|
||||||
stream_descriptor_list, packaging_params, &internal->fake_clock,
|
stream_descriptor_list, packaging_params, &internal->fake_clock,
|
||||||
internal->encryption_key_source.get(), internal->mpd_notifier.get(),
|
internal->encryption_key_source.get(), internal->mpd_notifier.get(),
|
||||||
|
|
|
@ -119,6 +119,7 @@
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'base/base.gyp:base',
|
'base/base.gyp:base',
|
||||||
'libpackager',
|
'libpackager',
|
||||||
|
'testing/gmock.gyp:gmock',
|
||||||
'testing/gtest.gyp:gtest',
|
'testing/gtest.gyp:gtest',
|
||||||
'testing/gtest.gyp:gtest_main',
|
'testing/gtest.gyp:gtest_main',
|
||||||
],
|
],
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "packager/hls/public/hls_params.h"
|
#include "packager/hls/public/hls_params.h"
|
||||||
|
#include "packager/file/public/buffer_callback_params.h"
|
||||||
#include "packager/media/public/chunking_params.h"
|
#include "packager/media/public/chunking_params.h"
|
||||||
#include "packager/media/public/crypto_params.h"
|
#include "packager/media/public/crypto_params.h"
|
||||||
#include "packager/media/public/mp4_output_params.h"
|
#include "packager/media/public/mp4_output_params.h"
|
||||||
|
@ -55,6 +56,9 @@ struct PackagingParams {
|
||||||
EncryptionParams encryption_params;
|
EncryptionParams encryption_params;
|
||||||
DecryptionParams decryption_params;
|
DecryptionParams decryption_params;
|
||||||
|
|
||||||
|
/// Buffer callback params.
|
||||||
|
BufferCallbackParams buffer_callback_params;
|
||||||
|
|
||||||
// Parameters for testing. Do not use in production.
|
// Parameters for testing. Do not use in production.
|
||||||
TestParams test_params;
|
TestParams test_params;
|
||||||
};
|
};
|
||||||
|
@ -63,8 +67,6 @@ struct PackagingParams {
|
||||||
struct StreamDescriptor {
|
struct StreamDescriptor {
|
||||||
/// Input/source media file path or network stream URL. Required.
|
/// Input/source media file path or network stream URL. Required.
|
||||||
std::string input;
|
std::string input;
|
||||||
// TODO(kqyang): Add support for feeding data through read func.
|
|
||||||
// std::function<int64_t(void* buffer, uint64_t length)> read_func;
|
|
||||||
|
|
||||||
/// Stream selector, can be `audio`, `video`, `text` or a zero based stream
|
/// Stream selector, can be `audio`, `video`, `text` or a zero based stream
|
||||||
/// index. Required.
|
/// index. Required.
|
||||||
|
@ -75,9 +77,6 @@ struct StreamDescriptor {
|
||||||
std::string output;
|
std::string output;
|
||||||
/// Specifies segment template. Can be empty.
|
/// Specifies segment template. Can be empty.
|
||||||
std::string segment_template;
|
std::string segment_template;
|
||||||
// TODO: Add support for writing data through write func.
|
|
||||||
// std::function<int64_t(const std::string& id, void* buffer, uint64_t
|
|
||||||
// length)> write_func;
|
|
||||||
|
|
||||||
/// Optional value which specifies output container format, e.g. "mp4". If not
|
/// Optional value which specifies output container format, e.g. "mp4". If not
|
||||||
/// specified, will detect from output / segment template name.
|
/// specified, will detect from output / segment template name.
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/base/files/file_util.h"
|
#include "packager/base/files/file_util.h"
|
||||||
|
@ -12,6 +13,14 @@
|
||||||
#include "packager/base/strings/string_number_conversions.h"
|
#include "packager/base/strings/string_number_conversions.h"
|
||||||
#include "packager/packager.h"
|
#include "packager/packager.h"
|
||||||
|
|
||||||
|
using testing::_;
|
||||||
|
using testing::Invoke;
|
||||||
|
using testing::MockFunction;
|
||||||
|
using testing::Return;
|
||||||
|
using testing::ReturnArg;
|
||||||
|
using testing::StrEq;
|
||||||
|
using testing::WithArgs;
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -154,6 +163,69 @@ TEST_F(PackagerTest, SegmentNotAlignedButSubsegmentAligned) {
|
||||||
ASSERT_EQ(error::INVALID_ARGUMENT, status.error_code());
|
ASSERT_EQ(error::INVALID_ARGUMENT, status.error_code());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(PackagerTest, WriteOutputToBuffer) {
|
||||||
|
auto packaging_params = SetupPackagingParams();
|
||||||
|
|
||||||
|
MockFunction<int64_t(const std::string& name, const void* buffer,
|
||||||
|
uint64_t length)>
|
||||||
|
mock_write_func;
|
||||||
|
packaging_params.buffer_callback_params.write_func =
|
||||||
|
mock_write_func.AsStdFunction();
|
||||||
|
EXPECT_CALL(mock_write_func, Call(StrEq(GetFullPath(kOutputVideo)), _, _))
|
||||||
|
.WillRepeatedly(ReturnArg<2>());
|
||||||
|
EXPECT_CALL(mock_write_func, Call(StrEq(GetFullPath(kOutputAudio)), _, _))
|
||||||
|
.WillRepeatedly(ReturnArg<2>());
|
||||||
|
EXPECT_CALL(mock_write_func, Call(StrEq(GetFullPath(kOutputMpd)), _, _))
|
||||||
|
.WillRepeatedly(ReturnArg<2>());
|
||||||
|
|
||||||
|
Packager packager;
|
||||||
|
ASSERT_EQ(Status::OK,
|
||||||
|
packager.Initialize(packaging_params, SetupStreamDescriptors()));
|
||||||
|
ASSERT_EQ(Status::OK, packager.Run());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PackagerTest, ReadFromBuffer) {
|
||||||
|
auto packaging_params = SetupPackagingParams();
|
||||||
|
|
||||||
|
MockFunction<int64_t(const std::string& name, void* buffer, uint64_t length)>
|
||||||
|
mock_read_func;
|
||||||
|
packaging_params.buffer_callback_params.read_func =
|
||||||
|
mock_read_func.AsStdFunction();
|
||||||
|
|
||||||
|
const std::string file_name = GetTestDataFilePath(kTestFile);
|
||||||
|
FILE* file_ptr =
|
||||||
|
base::OpenFile(base::FilePath::FromUTF8Unsafe(file_name), "rb");
|
||||||
|
ASSERT_TRUE(file_ptr);
|
||||||
|
EXPECT_CALL(mock_read_func, Call(StrEq(file_name), _, _))
|
||||||
|
.WillRepeatedly(
|
||||||
|
WithArgs<1, 2>(Invoke([file_ptr](void* buffer, uint64_t size) {
|
||||||
|
return fread(buffer, sizeof(char), size, file_ptr);
|
||||||
|
})));
|
||||||
|
|
||||||
|
Packager packager;
|
||||||
|
ASSERT_EQ(Status::OK,
|
||||||
|
packager.Initialize(packaging_params, SetupStreamDescriptors()));
|
||||||
|
ASSERT_EQ(Status::OK, packager.Run());
|
||||||
|
|
||||||
|
base::CloseFile(file_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PackagerTest, ReadFromBufferFailed) {
|
||||||
|
auto packaging_params = SetupPackagingParams();
|
||||||
|
|
||||||
|
MockFunction<int64_t(const std::string& name, void* buffer, uint64_t length)>
|
||||||
|
mock_read_func;
|
||||||
|
packaging_params.buffer_callback_params.read_func =
|
||||||
|
mock_read_func.AsStdFunction();
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_read_func, Call(_, _, _)).WillOnce(Return(-1));
|
||||||
|
|
||||||
|
Packager packager;
|
||||||
|
ASSERT_EQ(Status::OK,
|
||||||
|
packager.Initialize(packaging_params, SetupStreamDescriptors()));
|
||||||
|
ASSERT_EQ(error::FILE_FAILURE, packager.Run().error_code());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(kqyang): Add more tests.
|
// TODO(kqyang): Add more tests.
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
Loading…
Reference in New Issue