feat: Port app/job_manager to cmake and absl (#1249)
Issue #1047 (cmake) Issue #346 (absl)
This commit is contained in:
parent
4515a9834d
commit
ba51270b0e
|
@ -59,3 +59,4 @@ add_subdirectory(third_party)
|
||||||
add_subdirectory(tools)
|
add_subdirectory(tools)
|
||||||
add_subdirectory(utils)
|
add_subdirectory(utils)
|
||||||
add_subdirectory(version)
|
add_subdirectory(version)
|
||||||
|
add_subdirectory(app)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Copyright 2023 Google LLC. 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
|
||||||
|
|
||||||
|
add_library(libpackager STATIC
|
||||||
|
job_manager.cc
|
||||||
|
single_thread_job_manager.cc)
|
||||||
|
target_link_libraries(libpackager
|
||||||
|
absl::synchronization
|
||||||
|
media_chunking
|
||||||
|
media_origin
|
||||||
|
status)
|
|
@ -6,28 +6,49 @@
|
||||||
|
|
||||||
#include "packager/app/job_manager.h"
|
#include "packager/app/job_manager.h"
|
||||||
|
|
||||||
#include "packager/app/libcrypto_threading.h"
|
#include <set>
|
||||||
|
|
||||||
#include "packager/media/chunking/sync_point_queue.h"
|
#include "packager/media/chunking/sync_point_queue.h"
|
||||||
#include "packager/media/origin/origin_handler.h"
|
#include "packager/media/origin/origin_handler.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
Job::Job(const std::string& name, std::shared_ptr<OriginHandler> work)
|
Job::Job(const std::string& name,
|
||||||
: SimpleThread(name),
|
std::shared_ptr<OriginHandler> work,
|
||||||
|
OnCompleteFunction on_complete)
|
||||||
|
: name_(name),
|
||||||
work_(std::move(work)),
|
work_(std::move(work)),
|
||||||
wait_(base::WaitableEvent::ResetPolicy::MANUAL,
|
on_complete_(on_complete),
|
||||||
base::WaitableEvent::InitialState::NOT_SIGNALED) {
|
status_(error::Code::UNKNOWN, "Job uninitialized") {
|
||||||
DCHECK(work_);
|
DCHECK(work_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Status& Job::Initialize() {
|
||||||
|
status_ = work_->Initialize();
|
||||||
|
return status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Job::Start() {
|
||||||
|
thread_.reset(new std::thread(&Job::Run, this));
|
||||||
|
}
|
||||||
|
|
||||||
void Job::Cancel() {
|
void Job::Cancel() {
|
||||||
work_->Cancel();
|
work_->Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Job::Run() {
|
const Status& Job::Run() {
|
||||||
status_ = work_->Run();
|
if (status_.ok()) // initialized correctly
|
||||||
wait_.Signal();
|
status_ = work_->Run();
|
||||||
|
|
||||||
|
on_complete_(this);
|
||||||
|
|
||||||
|
return status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Job::Join() {
|
||||||
|
if (thread_)
|
||||||
|
thread_->join();
|
||||||
}
|
}
|
||||||
|
|
||||||
JobManager::JobManager(std::unique_ptr<SyncPointQueue> sync_points)
|
JobManager::JobManager(std::unique_ptr<SyncPointQueue> sync_points)
|
||||||
|
@ -35,81 +56,77 @@ JobManager::JobManager(std::unique_ptr<SyncPointQueue> sync_points)
|
||||||
|
|
||||||
void JobManager::Add(const std::string& name,
|
void JobManager::Add(const std::string& name,
|
||||||
std::shared_ptr<OriginHandler> handler) {
|
std::shared_ptr<OriginHandler> handler) {
|
||||||
// Stores Job entries for delayed construction of Job objects, to avoid
|
jobs_.emplace_back(new Job(
|
||||||
// setting up SimpleThread until we know all workers can be initialized
|
name, std::move(handler),
|
||||||
// successfully.
|
std::bind(&JobManager::OnJobComplete, this, std::placeholders::_1)));
|
||||||
job_entries_.push_back({name, std::move(handler)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Status JobManager::InitializeJobs() {
|
Status JobManager::InitializeJobs() {
|
||||||
Status status;
|
Status status;
|
||||||
for (const JobEntry& job_entry : job_entries_)
|
for (auto& job : jobs_)
|
||||||
status.Update(job_entry.worker->Initialize());
|
status.Update(job->Initialize());
|
||||||
if (!status.ok())
|
|
||||||
return status;
|
|
||||||
|
|
||||||
// Create Job objects after successfully initialized all workers.
|
|
||||||
for (const JobEntry& job_entry : job_entries_)
|
|
||||||
jobs_.emplace_back(new Job(job_entry.name, std::move(job_entry.worker)));
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status JobManager::RunJobs() {
|
Status JobManager::RunJobs() {
|
||||||
// We need to store the jobs and the waits separately in order to use the
|
std::set<Job*> active_jobs;
|
||||||
// |WaitMany| function. |WaitMany| takes an array of WaitableEvents but we
|
|
||||||
// need to access the jobs in order to join the thread and check the status.
|
|
||||||
// The indexes needs to be check in sync or else we won't be able to relate a
|
|
||||||
// WaitableEvent back to the job.
|
|
||||||
std::vector<Job*> active_jobs;
|
|
||||||
std::vector<base::WaitableEvent*> active_waits;
|
|
||||||
|
|
||||||
// Start every job and add it to the active jobs list so that we can wait
|
// Start every job and add it to the active jobs list so that we can wait
|
||||||
// on each one.
|
// on each one.
|
||||||
for (auto& job : jobs_) {
|
for (auto& job : jobs_) {
|
||||||
job->Start();
|
job->Start();
|
||||||
|
|
||||||
active_jobs.push_back(job.get());
|
active_jobs.insert(job.get());
|
||||||
active_waits.push_back(job->wait());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all jobs to complete or an error occurs.
|
// Wait for all jobs to complete or any job to error.
|
||||||
Status status;
|
Status status;
|
||||||
while (status.ok() && active_jobs.size()) {
|
{
|
||||||
// Wait for an event to finish and then update our status so that we can
|
absl::MutexLock lock(&mutex_);
|
||||||
// quit if something has gone wrong.
|
while (status.ok() && active_jobs.size()) {
|
||||||
const size_t done =
|
// any_job_complete_ is protected by mutex_.
|
||||||
base::WaitableEvent::WaitMany(active_waits.data(), active_waits.size());
|
any_job_complete_.Wait(&mutex_);
|
||||||
Job* job = active_jobs[done];
|
|
||||||
|
|
||||||
job->Join();
|
// complete_ is protected by mutex_.
|
||||||
status.Update(job->status());
|
for (const auto& entry : complete_) {
|
||||||
|
Job* job = entry.first;
|
||||||
// Remove the job and the wait from our tracking.
|
bool complete = entry.second;
|
||||||
active_jobs.erase(active_jobs.begin() + done);
|
if (complete) {
|
||||||
active_waits.erase(active_waits.begin() + done);
|
job->Join();
|
||||||
|
status.Update(job->status());
|
||||||
|
active_jobs.erase(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the main loop has exited and there are still jobs running,
|
// If the main loop has exited and there are still jobs running,
|
||||||
// we need to cancel them and clean-up.
|
// we need to cancel them and clean-up.
|
||||||
if (sync_points_)
|
if (sync_points_)
|
||||||
sync_points_->Cancel();
|
sync_points_->Cancel();
|
||||||
for (auto& job : active_jobs) {
|
|
||||||
job->Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& job : active_jobs) {
|
for (auto& job : active_jobs)
|
||||||
|
job->Cancel();
|
||||||
|
|
||||||
|
for (auto& job : active_jobs)
|
||||||
job->Join();
|
job->Join();
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void JobManager::OnJobComplete(Job* job) {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
// These are both protected by mutex_.
|
||||||
|
complete_[job] = true;
|
||||||
|
any_job_complete_.Signal();
|
||||||
|
}
|
||||||
|
|
||||||
void JobManager::CancelJobs() {
|
void JobManager::CancelJobs() {
|
||||||
if (sync_points_)
|
if (sync_points_)
|
||||||
sync_points_->Cancel();
|
sync_points_->Cancel();
|
||||||
for (auto& job : jobs_) {
|
|
||||||
|
for (auto& job : jobs_)
|
||||||
job->Cancel();
|
job->Cancel();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -7,11 +7,14 @@
|
||||||
#ifndef PACKAGER_APP_JOB_MANAGER_H_
|
#ifndef PACKAGER_APP_JOB_MANAGER_H_
|
||||||
#define PACKAGER_APP_JOB_MANAGER_H_
|
#define PACKAGER_APP_JOB_MANAGER_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "packager/base/threading/simple_thread.h"
|
#include "absl/synchronization/mutex.h"
|
||||||
#include "packager/status.h"
|
#include "packager/status/status.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
@ -21,33 +24,53 @@ class SyncPointQueue;
|
||||||
|
|
||||||
// A job is a single line of work that is expected to run in parallel with
|
// A job is a single line of work that is expected to run in parallel with
|
||||||
// other jobs.
|
// other jobs.
|
||||||
class Job : public base::SimpleThread {
|
class Job {
|
||||||
public:
|
public:
|
||||||
Job(const std::string& name, std::shared_ptr<OriginHandler> work);
|
typedef std::function<void(Job*)> OnCompleteFunction;
|
||||||
|
|
||||||
// Request that the job stops executing. This is only a request and
|
Job(const std::string& name,
|
||||||
// will not block. If you want to wait for the job to complete, use
|
std::shared_ptr<OriginHandler> work,
|
||||||
// |wait|.
|
OnCompleteFunction on_complete);
|
||||||
|
|
||||||
|
// Initialize the work object. Call before Start() or Run(). Updates status()
|
||||||
|
// and returns it for convenience.
|
||||||
|
const Status& Initialize();
|
||||||
|
|
||||||
|
// Begin the job in a new thread. This is only a request and will not block.
|
||||||
|
// If you want to wait for the job to complete, use |complete|.
|
||||||
|
// Use either Start() for threaded operation or Run() for non-threaded
|
||||||
|
// operation. DO NOT USE BOTH!
|
||||||
|
void Start();
|
||||||
|
|
||||||
|
// Run the job's work synchronously, blocking until complete. Updates status()
|
||||||
|
// and returns it for convenience.
|
||||||
|
// Use either Start() for threaded operation or Run() for non-threaded
|
||||||
|
// operation. DO NOT USE BOTH!
|
||||||
|
const Status& Run();
|
||||||
|
|
||||||
|
// Request that the job stops executing. This is only a request and will not
|
||||||
|
// block. If you want to wait for the job to complete, use |complete|.
|
||||||
void Cancel();
|
void Cancel();
|
||||||
|
|
||||||
// Get the current status of the job. If the job failed to initialize
|
// Join the thread, if any was started. Blocks until the thread has stopped.
|
||||||
// or encountered an error during execution this will return the error.
|
void Join();
|
||||||
|
|
||||||
|
// Get the current status of the job. If the job failed to initialize or
|
||||||
|
// encountered an error during execution this will return the error.
|
||||||
const Status& status() const { return status_; }
|
const Status& status() const { return status_; }
|
||||||
|
|
||||||
// If you want to wait for this job to complete, this will return the
|
// The name given to this job in the constructor.
|
||||||
// WaitableEvent you can wait on.
|
const std::string& name() const { return name_; }
|
||||||
base::WaitableEvent* wait() { return &wait_; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Job(const Job&) = delete;
|
Job(const Job&) = delete;
|
||||||
Job& operator=(const Job&) = delete;
|
Job& operator=(const Job&) = delete;
|
||||||
|
|
||||||
void Run() override;
|
std::string name_;
|
||||||
|
|
||||||
std::shared_ptr<OriginHandler> work_;
|
std::shared_ptr<OriginHandler> work_;
|
||||||
|
OnCompleteFunction on_complete_;
|
||||||
|
std::unique_ptr<std::thread> thread_;
|
||||||
Status status_;
|
Status status_;
|
||||||
|
|
||||||
base::WaitableEvent wait_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Similar to a thread pool, JobManager manages multiple jobs that are expected
|
// Similar to a thread pool, JobManager manages multiple jobs that are expected
|
||||||
|
@ -70,7 +93,7 @@ class JobManager {
|
||||||
// Initialize all registered jobs. If any job fails to initialize, this will
|
// Initialize all registered jobs. If any job fails to initialize, this will
|
||||||
// return the error and it will not be safe to call |RunJobs| as not all jobs
|
// return the error and it will not be safe to call |RunJobs| as not all jobs
|
||||||
// will be properly initialized.
|
// will be properly initialized.
|
||||||
virtual Status InitializeJobs();
|
Status InitializeJobs();
|
||||||
|
|
||||||
// Run all registered jobs. Before calling this make sure that
|
// Run all registered jobs. Before calling this make sure that
|
||||||
// |InitializedJobs| returned |Status::OK|. This call is blocking and will
|
// |InitializedJobs| returned |Status::OK|. This call is blocking and will
|
||||||
|
@ -87,16 +110,17 @@ class JobManager {
|
||||||
JobManager(const JobManager&) = delete;
|
JobManager(const JobManager&) = delete;
|
||||||
JobManager& operator=(const JobManager&) = delete;
|
JobManager& operator=(const JobManager&) = delete;
|
||||||
|
|
||||||
struct JobEntry {
|
void OnJobComplete(Job* job);
|
||||||
std::string name;
|
|
||||||
std::shared_ptr<OriginHandler> worker;
|
|
||||||
};
|
|
||||||
// Stores Job entries for delayed construction of Job object.
|
|
||||||
std::vector<JobEntry> job_entries_;
|
|
||||||
std::vector<std::unique_ptr<Job>> jobs_;
|
|
||||||
// Stored in JobManager so JobManager can cancel |sync_points| when any job
|
// Stored in JobManager so JobManager can cancel |sync_points| when any job
|
||||||
// fails or is cancelled.
|
// fails or is cancelled.
|
||||||
std::unique_ptr<SyncPointQueue> sync_points_;
|
std::unique_ptr<SyncPointQueue> sync_points_;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Job>> jobs_;
|
||||||
|
|
||||||
|
absl::Mutex mutex_;
|
||||||
|
std::map<Job*, bool> complete_ ABSL_GUARDED_BY(mutex_);
|
||||||
|
absl::CondVar any_job_complete_ ABSL_GUARDED_BY(mutex_);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Google LLLC All rights reserved.
|
// Copyright 2020 Google LLC. All rights reserved.
|
||||||
//
|
//
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
|
@ -16,17 +16,12 @@ SingleThreadJobManager::SingleThreadJobManager(
|
||||||
std::unique_ptr<SyncPointQueue> sync_points)
|
std::unique_ptr<SyncPointQueue> sync_points)
|
||||||
: JobManager(std::move(sync_points)) {}
|
: JobManager(std::move(sync_points)) {}
|
||||||
|
|
||||||
Status SingleThreadJobManager::InitializeJobs() {
|
|
||||||
Status status;
|
|
||||||
for (const JobEntry& job_entry : job_entries_)
|
|
||||||
status.Update(job_entry.worker->Initialize());
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status SingleThreadJobManager::RunJobs() {
|
Status SingleThreadJobManager::RunJobs() {
|
||||||
Status status;
|
Status status;
|
||||||
for (const JobEntry& job_entry : job_entries_)
|
|
||||||
status.Update(job_entry.worker->Run());
|
for (auto& job : jobs_)
|
||||||
|
status.Update(job->Run());
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Google LLLC All rights reserved.
|
// Copyright 2020 Google LLC. All rights reserved.
|
||||||
//
|
//
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
|
@ -22,7 +22,7 @@ class SingleThreadJobManager : public JobManager {
|
||||||
// fails or is cancelled. It can be NULL.
|
// fails or is cancelled. It can be NULL.
|
||||||
explicit SingleThreadJobManager(std::unique_ptr<SyncPointQueue> sync_points);
|
explicit SingleThreadJobManager(std::unique_ptr<SyncPointQueue> sync_points);
|
||||||
|
|
||||||
Status InitializeJobs() override;
|
// Run all registered jobs serially in this thread.
|
||||||
Status RunJobs() override;
|
Status RunJobs() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,26 +4,26 @@
|
||||||
# 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
|
||||||
|
|
||||||
add_library(chunking STATIC
|
add_library(media_chunking STATIC
|
||||||
chunking_handler.cc
|
chunking_handler.cc
|
||||||
cue_alignment_handler.cc
|
cue_alignment_handler.cc
|
||||||
sync_point_queue.cc
|
sync_point_queue.cc
|
||||||
text_chunker.cc
|
text_chunker.cc
|
||||||
)
|
)
|
||||||
target_link_libraries(chunking
|
target_link_libraries(media_chunking
|
||||||
media_base
|
media_base
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(chunking_unittest
|
add_executable(media_chunking_unittest
|
||||||
chunking_handler_unittest.cc
|
chunking_handler_unittest.cc
|
||||||
cue_alignment_handler_unittest.cc
|
cue_alignment_handler_unittest.cc
|
||||||
text_chunker_unittest.cc
|
text_chunker_unittest.cc
|
||||||
)
|
)
|
||||||
target_link_libraries(chunking_unittest
|
target_link_libraries(media_chunking_unittest
|
||||||
gmock
|
gmock
|
||||||
gtest
|
gtest
|
||||||
gtest_main
|
gtest_main
|
||||||
|
media_chunking
|
||||||
media_handler_test_base
|
media_handler_test_base
|
||||||
chunking
|
|
||||||
)
|
)
|
||||||
add_gtest(chunking_unittest)
|
add_gtest(media_chunking_unittest)
|
||||||
|
|
|
@ -4,7 +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
|
||||||
|
|
||||||
add_library(codecs STATIC
|
add_library(media_codecs STATIC
|
||||||
aac_audio_specific_config.cc
|
aac_audio_specific_config.cc
|
||||||
ac3_audio_util.cc
|
ac3_audio_util.cc
|
||||||
av1_codec_configuration_record.cc
|
av1_codec_configuration_record.cc
|
||||||
|
@ -31,10 +31,10 @@ add_library(codecs STATIC
|
||||||
vp9_parser.cc
|
vp9_parser.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(codecs
|
target_link_libraries(media_codecs
|
||||||
media_base)
|
media_base)
|
||||||
|
|
||||||
add_executable(codecs_unittest
|
add_executable(media_codecs_unittest
|
||||||
aac_audio_specific_config_unittest.cc
|
aac_audio_specific_config_unittest.cc
|
||||||
ac3_audio_util_unittest.cc
|
ac3_audio_util_unittest.cc
|
||||||
av1_codec_configuration_record_unittest.cc
|
av1_codec_configuration_record_unittest.cc
|
||||||
|
@ -59,11 +59,11 @@ add_executable(codecs_unittest
|
||||||
vp9_parser_unittest.cc
|
vp9_parser_unittest.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(codecs_unittest
|
target_link_libraries(media_codecs_unittest
|
||||||
codecs
|
|
||||||
gmock
|
gmock
|
||||||
gtest
|
gtest
|
||||||
gtest_main
|
gtest_main
|
||||||
|
media_codecs
|
||||||
test_data_util)
|
test_data_util)
|
||||||
|
|
||||||
add_gtest(codecs_unittest)
|
add_gtest(media_codecs_unittest)
|
||||||
|
|
|
@ -10,8 +10,8 @@ add_library(media_crypto STATIC
|
||||||
sample_aes_ec3_cryptor.cc
|
sample_aes_ec3_cryptor.cc
|
||||||
subsample_generator.cc)
|
subsample_generator.cc)
|
||||||
target_link_libraries(media_crypto
|
target_link_libraries(media_crypto
|
||||||
codecs
|
|
||||||
media_base
|
media_base
|
||||||
|
media_codecs
|
||||||
absl::base
|
absl::base
|
||||||
glog)
|
glog)
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ add_executable(media_crypto_unittest
|
||||||
sample_aes_ec3_cryptor_unittest.cc
|
sample_aes_ec3_cryptor_unittest.cc
|
||||||
subsample_generator_unittest.cc)
|
subsample_generator_unittest.cc)
|
||||||
target_link_libraries(media_crypto_unittest
|
target_link_libraries(media_crypto_unittest
|
||||||
codecs
|
|
||||||
media_base
|
media_base
|
||||||
|
media_codecs
|
||||||
media_crypto
|
media_crypto
|
||||||
media_handler_test_base
|
media_handler_test_base
|
||||||
status
|
status
|
||||||
|
|
|
@ -17,7 +17,7 @@ target_link_libraries(media_event
|
||||||
file
|
file
|
||||||
mpd_media_info_proto
|
mpd_media_info_proto
|
||||||
media_base
|
media_base
|
||||||
codecs
|
media_codecs
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(mock_muxer_listener STATIC
|
add_library(mock_muxer_listener STATIC
|
||||||
|
|
|
@ -30,7 +30,7 @@ target_link_libraries(formats_webm
|
||||||
webm
|
webm
|
||||||
file
|
file
|
||||||
media_base
|
media_base
|
||||||
codecs
|
media_codecs
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(webm_unittest
|
add_executable(webm_unittest
|
||||||
|
|
Loading…
Reference in New Issue